diff --git a/calendarsupport/calendarutils.cpp b/calendarsupport/calendarutils.cpp index d67a1eba38..41fe810e54 100644 --- a/calendarsupport/calendarutils.cpp +++ b/calendarsupport/calendarutils.cpp @@ -1,269 +1,269 @@ /* Copyright (C) 2010 Bertjan Broeksema This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ // NOTE: The code of the following methods is taken from // kdepim/korganizer/calendarview.cpp: // - makeIndependent (was incidence_unsub) // - makeChildrenIndependent #include "calendarutils.h" #include "utils.h" #include #include #include #include #include using namespace CalendarSupport; using namespace KCalCore; /// CalendarUtilsPrivate struct MultiChange { Akonadi::Item parent; QVector children; bool success; explicit MultiChange( const Akonadi::Item &parent = Akonadi::Item() ) : parent( parent ), success( true ) {} bool inProgress() const { return parent.isValid() && !children.isEmpty(); } }; namespace CalendarSupport { class CalendarUtilsPrivate { public: /// Methods CalendarUtilsPrivate( const Akonadi::ETMCalendar::Ptr &calendar, CalendarUtils *qq ); void handleChangeFinish( int changeId, const Akonadi::Item &item, Akonadi::IncidenceChanger::ResultCode resultCode, const QString &errorString ); bool purgeCompletedSubTodos( const KCalCore::Todo::Ptr &todo, bool &allPurged ); /// Members Akonadi::ETMCalendar::Ptr mCalendar; Akonadi::IncidenceChanger *mChanger; MultiChange mMultiChange; private: CalendarUtils *const q_ptr; Q_DECLARE_PUBLIC( CalendarUtils ) }; } CalendarUtilsPrivate::CalendarUtilsPrivate( const Akonadi::ETMCalendar::Ptr &calendar, CalendarUtils *qq ) : mCalendar( calendar ), mChanger( new Akonadi::IncidenceChanger( qq ) ), q_ptr( qq ) { Q_Q( CalendarUtils ); Q_ASSERT( mCalendar ); q->connect( mChanger, SIGNAL(modifyFinished(int,Akonadi::Item,Akonadi::IncidenceChanger::ResultCode,QString)), SLOT(handleChangeFinish(int,Akonadi::Item,Akonadi::IncidenceChanger::ResultCode,QString)) ); } void CalendarUtilsPrivate::handleChangeFinish( int, const Akonadi::Item &item, Akonadi::IncidenceChanger::ResultCode resultCode, const QString &errorString ) { Q_Q( CalendarUtils ); const bool success = resultCode == Akonadi::IncidenceChanger::ResultCodeSuccess; if ( mMultiChange.inProgress() ) { mMultiChange.children.remove( mMultiChange.children.indexOf( item.id() ) ); mMultiChange.success = mMultiChange.success && success; // Are we still in progress? if ( !mMultiChange.inProgress() ) { const Akonadi::Item parent = mMultiChange.parent; const bool success = mMultiChange.success; // Reset the multi change. mMultiChange = MultiChange(); Q_ASSERT( !mMultiChange.inProgress() ); if ( success ) { kDebug() << "MultiChange finished"; emit q->actionFinished( parent ); } else { kDebug() << "MultiChange failed"; emit q->actionFailed( parent, QString() ); } } } else { if ( success ) { kDebug() << "Change finished"; emit q->actionFinished( item ); } else { kDebug() << "Change failed"; emit q->actionFailed( Akonadi::Item(), errorString ); } } } bool CalendarUtilsPrivate::purgeCompletedSubTodos( const KCalCore::Todo::Ptr &todo, bool &allPurged ) { if ( !todo ) { return true; } bool deleteThisTodo = true; - Akonadi::Item::List subTodos = mCalendar->childItems( todo->uid() ); + Akonadi::Item::List subTodos = mCalendar->childItems( todo ); foreach ( const Akonadi::Item &item, subTodos ) { if ( CalendarSupport::hasTodo( item ) ) { deleteThisTodo &= purgeCompletedSubTodos( item.payload(), allPurged ); } } if ( deleteThisTodo ) { if ( todo->isCompleted() ) { if ( !mChanger->deleteIncidence( mCalendar->item( todo ), 0 ) ) { allPurged = false; } } else { deleteThisTodo = false; } } else { if ( todo->isCompleted() ) { allPurged = false; } } return deleteThisTodo; } /// CalendarUtils CalendarUtils::CalendarUtils( const Akonadi::ETMCalendar::Ptr &calendar, QObject *parent ) : QObject( parent ), d_ptr( new CalendarUtilsPrivate( calendar, this ) ) { Q_ASSERT( calendar ); } CalendarUtils::~CalendarUtils() { delete d_ptr; } Akonadi::ETMCalendar::Ptr CalendarUtils::calendar() const { Q_D( const CalendarUtils ); return d->mCalendar; } bool CalendarUtils::makeIndependent( const Akonadi::Item &item ) { Q_D( CalendarUtils ); Q_ASSERT( item.isValid() ); if ( d->mMultiChange.inProgress() && !d->mMultiChange.children.contains( item.id() ) ) { return false; } const Incidence::Ptr inc = CalendarSupport::incidence( item ); if ( !inc || inc->relatedTo().isEmpty() ) { return false; } Incidence::Ptr oldInc( inc->clone() ); inc->setRelatedTo( QString() ); return d->mChanger->modifyIncidence( item, oldInc ); } bool CalendarUtils::makeChildrenIndependent( const Akonadi::Item &item ) { Q_D( CalendarUtils ); Q_ASSERT( item.isValid() ); if ( d->mMultiChange.inProgress() ) { return false; } const Incidence::Ptr inc = CalendarSupport::incidence( item ); const Akonadi::Item::List subIncs = d->mCalendar->childItems( item.id() ); if ( !inc || subIncs.isEmpty() ) { return false; } d->mMultiChange = MultiChange( item ); bool allStarted = true; foreach ( const Akonadi::Item &subInc, subIncs ) { d->mMultiChange.children.append( subInc.id() ); allStarted = allStarted && makeIndependent( subInc ); } Q_ASSERT( allStarted ); // OKay, maybe we should not assert here, but one or // changes could have been started, so just returning // false isn't suitable either. return true; } /// Todo specific methods. void CalendarUtils::purgeCompletedTodos() { Q_D( CalendarUtils ); bool allDeleted = true; // startMultiModify( i18n( "Purging completed to-dos" ) ); KCalCore::Todo::List todos = calendar()->rawTodos(); KCalCore::Todo::List rootTodos; foreach ( const KCalCore::Todo::Ptr &todo, todos ) { if ( todo && todo->relatedTo().isEmpty() ) { // top level todo //REVIEW(AKONADI_PORT) rootTodos.append( todo ); } } // now that we have a list of all root todos, check them and their children foreach ( const KCalCore::Todo::Ptr &todo, rootTodos ) { d->purgeCompletedSubTodos( todo, allDeleted ); } // endMultiModify(); if ( !allDeleted ) { KMessageBox::information( 0, i18nc( "@info", "Unable to purge to-dos with uncompleted children." ), i18nc( "@title:window", "Delete To-do" ), QLatin1String("UncompletedChildrenPurgeTodos") ); } } #include "moc_calendarutils.cpp" diff --git a/calendarsupport/eventarchiver.cpp b/calendarsupport/eventarchiver.cpp index b68a29cff9..a536ff3a76 100644 --- a/calendarsupport/eventarchiver.cpp +++ b/calendarsupport/eventarchiver.cpp @@ -1,335 +1,335 @@ /* Copyright (c) 2000,2001 Cornelius Schumacher Copyright (c) 2004 David Faure Copyright (C) 2004 Reinhold Kainhofer This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #include "eventarchiver.h" #include "kcalprefs.h" #include "utils.h" #include #include #include #include #include #include #include #include #include #include #include using namespace KCalCore; using namespace KCalUtils; using namespace CalendarSupport; class GroupwareScoppedDisabler { public: GroupwareScoppedDisabler(Akonadi::IncidenceChanger *changer) : m_changer(changer) { m_wasEnabled = m_changer->groupwareCommunication(); m_changer->setGroupwareCommunication(false); } ~GroupwareScoppedDisabler() { m_changer->setGroupwareCommunication(m_wasEnabled); } bool m_wasEnabled; Akonadi::IncidenceChanger *m_changer; }; EventArchiver::EventArchiver( QObject *parent ) : QObject( parent ) { } EventArchiver::~EventArchiver() { } void EventArchiver::runOnce( const Akonadi::ETMCalendar::Ptr &calendar, Akonadi::IncidenceChanger *changer, const QDate &limitDate, QWidget *widget ) { run( calendar, changer, limitDate, widget, true, true ); } void EventArchiver::runAuto( const Akonadi::ETMCalendar::Ptr &calendar, Akonadi::IncidenceChanger *changer, QWidget *widget, bool withGUI ) { QDate limitDate( QDate::currentDate() ); const int expiryTime = KCalPrefs::instance()->mExpiryTime; switch ( KCalPrefs::instance()->mExpiryUnit ) { case KCalPrefs::UnitDays: // Days limitDate = limitDate.addDays( -expiryTime ); break; case KCalPrefs::UnitWeeks: // Weeks limitDate = limitDate.addDays( -expiryTime * 7 ); break; case KCalPrefs::UnitMonths: // Months limitDate = limitDate.addMonths( -expiryTime ); break; default: return; } run( calendar, changer, limitDate, widget, withGUI, false ); } void EventArchiver::run( const Akonadi::ETMCalendar::Ptr &calendar, Akonadi::IncidenceChanger *changer, const QDate &limitDate, QWidget *widget, bool withGUI, bool errorIfNone ) { GroupwareScoppedDisabler disabler(changer); // Disables groupware communication temporarily // We need to use rawEvents, otherwise events hidden by filters will not be archived. KCalCore::Event::List events; KCalCore::Todo::List todos; KCalCore::Journal::List journals; if ( KCalPrefs::instance()->mArchiveEvents ) { events = calendar->rawEvents( QDate( 1769, 12, 1 ), // #29555, also advertised by the "limitDate not included" in the class docu limitDate.addDays( -1 ), CalendarSupport::KCalPrefs::instance()->timeSpec(), true ); } if ( KCalPrefs::instance()->mArchiveTodos ) { KCalCore::Todo::List rawTodos = calendar->rawTodos(); foreach( const KCalCore::Todo::Ptr &todo, rawTodos ) { Q_ASSERT( todo ); if ( isSubTreeComplete( calendar, todo, limitDate ) ) { todos.append( todo ); } } } const KCalCore::Incidence::List incidences = calendar->mergeIncidenceList( events, todos, journals ); kDebug() << "archiving incidences before" << limitDate << " ->" << incidences.count() <<" incidences found."; if ( incidences.isEmpty() ) { if ( withGUI && errorIfNone ) { KMessageBox::information( widget, i18n( "There are no items before %1", KGlobal::locale()->formatDate( limitDate ) ), QLatin1String("ArchiverNoIncidences") ); } return; } switch ( KCalPrefs::instance()->mArchiveAction ) { case KCalPrefs::actionDelete: deleteIncidences( changer, limitDate, widget, calendar->itemList( incidences ), withGUI ); break; case KCalPrefs::actionArchive: archiveIncidences( calendar, changer, limitDate, widget, incidences, withGUI ); break; } } void EventArchiver::deleteIncidences( Akonadi::IncidenceChanger *changer, const QDate &limitDate, QWidget *widget, const Akonadi::Item::List &items, bool withGUI ) { QStringList incidenceStrs; Akonadi::Item::List::ConstIterator it; Akonadi::Item::List::ConstIterator end( items.constEnd() ); for ( it = items.constBegin(); it != end; ++it ) { incidenceStrs.append( CalendarSupport::incidence( *it )->summary() ); } if ( withGUI ) { const int result = KMessageBox::warningContinueCancelList( widget, i18n( "Delete all items before %1 without saving?\n" "The following items will be deleted:", KGlobal::locale()->formatDate( limitDate ) ), incidenceStrs, i18n( "Delete Old Items" ), KStandardGuiItem::del() ); if ( result != KMessageBox::Continue ) { return; } } changer->deleteIncidences( items, /**parent=*/widget ); // TODO: emit only after hearing back from incidence changer emit eventsDeleted(); } void EventArchiver::archiveIncidences( const Akonadi::ETMCalendar::Ptr &calendar, Akonadi::IncidenceChanger *changer, const QDate &limitDate, QWidget *widget, const KCalCore::Incidence::List &incidences, bool withGUI ) { Q_UNUSED( limitDate ); Q_UNUSED( withGUI ); FileStorage storage( calendar ); QString tmpFileName; // KSaveFile cannot be called with an open File Handle on Windows. // So we use KTemporaryFile only to generate a unique filename // and then close/delete the file again. This file must be deleted // here. { KTemporaryFile tmpFile; tmpFile.open(); tmpFileName = tmpFile.fileName(); } // Save current calendar to disk storage.setFileName( tmpFileName ); if ( !storage.save() ) { kDebug() << "Can't save calendar to temp file"; return; } // Duplicate current calendar by loading in new calendar object MemoryCalendar::Ptr archiveCalendar( new MemoryCalendar( CalendarSupport::KCalPrefs::instance()->timeSpec() ) ); FileStorage archiveStore( archiveCalendar ); archiveStore.setFileName( tmpFileName ); ICalFormat *format = new ICalFormat(); archiveStore.setSaveFormat( format ); if ( !archiveStore.load() ) { kDebug() << "Can't load calendar from temp file"; QFile::remove( tmpFileName ); return; } // Strip active events from calendar so that only events to be archived // remain. This is not really efficient, but there is no other easy way. QStringList uids; Incidence::List allIncidences = archiveCalendar->rawIncidences(); foreach ( const KCalCore::Incidence::Ptr &incidence, incidences ) { uids.append( incidence->uid() ); } foreach ( const KCalCore::Incidence::Ptr &incidence, allIncidences ) { if ( !uids.contains( incidence->uid() ) ) { archiveCalendar->deleteIncidence( incidence ); } } // Get or create the archive file KUrl archiveURL( KCalPrefs::instance()->mArchiveFile ); QString archiveFile; // There is no KIO::NetAccess availabe for Windows CE if ( KIO::NetAccess::exists( archiveURL, KIO::NetAccess::SourceSide, widget ) ) { if( !KIO::NetAccess::download( archiveURL, archiveFile, widget ) ) { kDebug() << "Can't download archive file"; QFile::remove( tmpFileName ); return; } // Merge with events to be archived. archiveStore.setFileName( archiveFile ); if ( !archiveStore.load() ) { kDebug() << "Can't merge with archive file"; QFile::remove( tmpFileName ); return; } } else { archiveFile = tmpFileName; } // Save archive calendar if ( !archiveStore.save() ) { QString errmess; if ( format->exception() ) { errmess = Stringify::errorMessage( *format->exception() ); } else { errmess = i18nc( "save failure cause unknown", "Reason unknown" ); } KMessageBox::error( widget, i18n( "Cannot write archive file %1. %2", archiveStore.fileName(), errmess ) ); QFile::remove( tmpFileName ); return; } // Upload if necessary KUrl srcUrl; srcUrl.setPath( archiveFile ); if ( srcUrl != archiveURL ) { if ( !KIO::NetAccess::upload( archiveFile, archiveURL, widget ) ) { KMessageBox::error( widget, i18n( "Cannot write archive. %1", KIO::NetAccess::lastErrorString() ) ); QFile::remove( tmpFileName ); return; } } KIO::NetAccess::removeTempFile( archiveFile ); QFile::remove( tmpFileName ); // We don't want it to ask to send invitations for each incidence. changer->startAtomicOperation( i18n( "Archiving events" ) ); // Delete archived events from calendar Akonadi::Item::List items = calendar->itemList( incidences ); foreach ( const Akonadi::Item &item, items ) { changer->deleteIncidence( item, widget ); } // TODO: emit only after hearing back from incidence changer changer->endAtomicOperation(); emit eventsDeleted(); } bool EventArchiver::isSubTreeComplete( const Akonadi::ETMCalendar::Ptr &calendar, const Todo::Ptr &todo, const QDate &limitDate, QStringList checkedUids ) const { if ( !todo->isCompleted() || todo->completed().date() >= limitDate ) { return false; } // This QList is only to prevent infinit recursion if ( checkedUids.contains( todo->uid() ) ) { // Probably will never happen, calendar.cpp checks for this kWarning() << "To-do hierarchy loop detected!"; return false; } checkedUids.append( todo->uid() ); - KCalCore::Incidence::List childs = calendar->childIncidences( todo->uid() ); + KCalCore::Incidence::List childs = calendar->childIncidences( todo ); foreach ( const KCalCore::Incidence::Ptr &incidence, childs ) { const Todo::Ptr t = incidence.dynamicCast(); if ( t && !isSubTreeComplete( calendar, t, limitDate, checkedUids ) ) { return false; } } return true; } diff --git a/calendarsupport/printing/calprintdefaultplugins.cpp b/calendarsupport/printing/calprintdefaultplugins.cpp index 8dfa41fedb..c197596b2f 100644 --- a/calendarsupport/printing/calprintdefaultplugins.cpp +++ b/calendarsupport/printing/calprintdefaultplugins.cpp @@ -1,1662 +1,1662 @@ /* Copyright (c) 1998 Preston Brown Copyright (C) 2003 Reinhold Kainhofer Copyright (c) 2003 Cornelius Schumacher Copyright (c) 2008 Ron Goodheart Copyright (c) 2010 Laurent Montel Copyright (c) 2012-2013 Allen Winter This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #include "calprintdefaultplugins.h" #include "kcalprefs.h" #include "utils.h" #include #include #include #include #include #include #include #include #include #include #include using namespace CalendarSupport; static QString cleanStr( const QString &instr ) { QString ret = instr; return ret.replace( QLatin1Char('\n'), QLatin1Char(' ') ); } /************************************************************** * Print Incidence **************************************************************/ CalPrintIncidence::CalPrintIncidence() : CalPrintPluginBase() { } CalPrintIncidence::~CalPrintIncidence() { } QWidget *CalPrintIncidence::createConfigWidget( QWidget *w ) { return new CalPrintIncidenceConfig( w ); } void CalPrintIncidence::readSettingsWidget() { CalPrintIncidenceConfig *cfg = dynamic_cast( ( QWidget* )mConfigWidget ); if ( cfg ) { mUseColors = cfg->mColors->isChecked(); mPrintFooter = cfg->mPrintFooter->isChecked(); mShowOptions = cfg->mShowDetails->isChecked(); mShowSubitemsNotes = cfg->mShowSubitemsNotes->isChecked(); mShowAttendees = cfg->mShowAttendees->isChecked(); mShowAttachments = cfg->mShowAttachments->isChecked(); mShowNoteLines = cfg->mShowNoteLines->isChecked(); } } void CalPrintIncidence::setSettingsWidget() { CalPrintIncidenceConfig *cfg = dynamic_cast( ( QWidget* )mConfigWidget ); if ( cfg ) { cfg->mColors->setChecked( mUseColors ); cfg->mPrintFooter->setChecked( mPrintFooter ); cfg->mShowDetails->setChecked( mShowOptions ); cfg->mShowSubitemsNotes->setChecked( mShowSubitemsNotes ); cfg->mShowAttendees->setChecked( mShowAttendees ); cfg->mShowAttachments->setChecked( mShowAttachments ); cfg->mShowNoteLines->setChecked( mShowNoteLines ); } } void CalPrintIncidence::loadConfig() { if ( mConfig ) { KConfigGroup grp( mConfig, groupName() ); mShowOptions = grp.readEntry( "Show Options", false ); mShowSubitemsNotes = grp.readEntry( "Show Subitems and Notes", false ); mShowAttendees = grp.readEntry( "Use Attendees", false ); mShowAttachments = grp.readEntry( "Use Attachments", false ); mShowNoteLines = grp.readEntry( "Note Lines", false ); } setSettingsWidget(); } void CalPrintIncidence::saveConfig() { readSettingsWidget(); if ( mConfig ) { KConfigGroup grp( mConfig, groupName() ); grp.writeEntry( "Show Options", mShowOptions ); grp.writeEntry( "Show Subitems and Notes", mShowSubitemsNotes ); grp.writeEntry( "Use Attendees", mShowAttendees ); grp.writeEntry( "Use Attachments", mShowAttachments ); grp.writeEntry( "Note Lines", mShowNoteLines ); } } class TimePrintStringsVisitor : public KCalCore::Visitor { public: TimePrintStringsVisitor() {} bool act( KCalCore::IncidenceBase::Ptr incidence ) { return incidence->accept( *this, incidence ); } QString mStartCaption, mStartString; QString mEndCaption, mEndString; QString mDurationCaption, mDurationString; protected: bool visit( KCalCore::Event::Ptr event ) { if ( event->dtStart().isValid() ) { mStartCaption = i18n( "Start date: " ); mStartString = KCalUtils::IncidenceFormatter::dateTimeToString( event->dtStart(), event->allDay(), false ); } else { mStartCaption = i18n( "No start date" ); mStartString.clear(); } if ( event->hasEndDate() ) { mEndCaption = i18n( "End date: " ); mEndString = KCalUtils::IncidenceFormatter::dateTimeToString( event->dtEnd(), event->allDay(), false ); } else if ( event->hasDuration() ) { mEndCaption = i18n( "Duration: " ); int mins = event->duration().asSeconds() / 60; if ( mins >= 60 ) { mEndString += i18np( "1 hour ", "%1 hours ", mins / 60 ); } if ( mins % 60 > 0 ) { mEndString += i18np( "1 minute ", "%1 minutes ", mins % 60 ); } } else { mEndCaption = i18n( "No end date" ); mEndString.clear(); } return true; } bool visit( KCalCore::Todo::Ptr todo ) { if ( todo->hasStartDate() ) { mStartCaption = i18n( "Start date: " ); mStartString = KCalUtils::IncidenceFormatter::dateTimeToString( todo->dtStart(), todo->allDay(), false ); } else { mStartCaption = i18n( "No start date" ); mStartString.clear(); } if ( todo->hasDueDate() ) { mEndCaption = i18n( "Due date: " ); mEndString = KCalUtils::IncidenceFormatter::dateTimeToString( todo->dtDue(), todo->allDay(), false ); } else { mEndCaption = i18n( "No due date" ); mEndString.clear(); } return true; } bool visit( KCalCore::Journal::Ptr journal ) { mStartCaption = i18n( "Start date: " ); mStartString = KCalUtils::IncidenceFormatter::dateTimeToString( journal->dtStart(), journal->allDay(), false ); mEndCaption.clear(); mEndString.clear(); return true; } bool visit( KCalCore::FreeBusy::Ptr fb ) { Q_UNUSED( fb ); return true; } }; int CalPrintIncidence::printCaptionAndText( QPainter &p, const QRect &box, const QString &caption, const QString &text, QFont captionFont, QFont textFont ) { QFontMetrics captionFM( captionFont ); int textWd = captionFM.width( caption ); QRect textRect( box ); QFont oldFont( p.font() ); p.setFont( captionFont ); p.drawText( box, Qt::AlignLeft|Qt::AlignTop|Qt::TextSingleLine, caption ); if ( !text.isEmpty() ) { textRect.setLeft( textRect.left() + textWd ); p.setFont( textFont ); p.drawText( textRect, Qt::AlignLeft|Qt::AlignTop|Qt::TextSingleLine, text ); } p.setFont( oldFont ); return textRect.bottom(); } void CalPrintIncidence::print( QPainter &p, int width, int height ) { QFont oldFont( p.font() ); QFont textFont( QLatin1String("sans-serif"), 11, QFont::Normal ); QFont captionFont( QLatin1String("sans-serif"), 11, QFont::Bold ); p.setFont( textFont ); int lineHeight = p.fontMetrics().lineSpacing(); QString cap, txt; KCalCore::Incidence::List::ConstIterator it; for ( it = mSelectedIncidences.constBegin(); it != mSelectedIncidences.constEnd(); ++it ) { // don't do anything on a 0-pointer! if ( !(*it) ) { continue; } if ( it != mSelectedIncidences.constBegin() ) { mPrinter->newPage(); } const bool isJournal = ( (*it)->type() == KCalCore::Incidence::TypeJournal ); // PAGE Layout (same for landscape and portrait! astonishingly, it looks good with both!): // +-----------------------------------+ // | Header: Summary | // +===================================+ // | start: ______ end: _________ | // | repeats: ___________________ | // | reminder: __________________ | // +-----------------------------------+ // | Location: ______________________ | // +------------------------+----------+ // | Description: | Notes or | // | | Subitems | // | | | // | | | // | | | // | | | // | | | // | | | // | | | // | | | // +------------------------+----------+ // | Attachments: | Settings | // | | | // +------------------------+----------+ // | Attendees: | // | | // +-----------------------------------+ // | Categories: _____________________ | // +-----------------------------------+ QRect box( 0, 0, width, height ); QRect titleBox( box ); titleBox.setHeight( headerHeight() ); QColor headerColor = categoryBgColor( *it ); // Draw summary as header, no small calendars in title bar, expand height if needed int titleBottom = drawHeader( p, (*it)->summary(), QDate(), QDate(), titleBox, true, headerColor ); titleBox.setBottom( titleBottom ); QRect timesBox( titleBox ); timesBox.setTop( titleBox.bottom() + padding() ); timesBox.setHeight( height / 8 ); TimePrintStringsVisitor stringVis; int h = timesBox.top(); if ( stringVis.act( *it ) ) { QRect textRect( timesBox.left() + padding(), timesBox.top() + padding(), 0, lineHeight ); textRect.setRight( timesBox.center().x() ); h = printCaptionAndText( p, textRect, stringVis.mStartCaption, stringVis.mStartString, captionFont, textFont ); textRect.setLeft( textRect.right() ); textRect.setRight( timesBox.right() - padding() ); h = qMax( printCaptionAndText( p, textRect, stringVis.mEndCaption, stringVis.mEndString, captionFont, textFont ), h ); } // Recurrence Printing if ( (*it)->recurs() ) { QRect recurBox( timesBox.left() + padding(), h + padding(), timesBox.right() - padding(), lineHeight ); KCalCore::Recurrence *recurs = (*it)->recurrence(); QString displayString = KCalUtils::IncidenceFormatter::recurrenceString((*it)); // exception dates QString exceptString; if ( !recurs->exDates().isEmpty() ) { exceptString = i18nc( "except for listed dates", " except" ); for ( int i = 0; i < recurs->exDates().size(); ++i ) { exceptString.append( QLatin1String(" ") ); exceptString.append( KGlobal::locale()->formatDate( recurs->exDates()[i], KLocale::ShortDate ) ); } } displayString.append( exceptString ); h = qMax( printCaptionAndText( p, recurBox, i18n( "Repeats: " ), displayString, captionFont, textFont ), h ); } if ( !isJournal ) { // Alarms Printing QRect alarmBox( timesBox.left() + padding(), h + padding(), timesBox.right() - padding(), lineHeight ); KCalCore::Alarm::List alarms = (*it)->alarms(); if ( alarms.count() == 0 ) { cap = i18n( "No reminders" ); txt.clear(); } else { cap = i18np( "Reminder: ", "%1 reminders: ", alarms.count() ); QStringList alarmStrings; KCalCore::Alarm::List::ConstIterator it; for ( it = alarms.constBegin(); it != alarms.constEnd(); ++it ) { KCalCore::Alarm::Ptr alarm = *it; // Alarm offset, copied from koeditoralarms.cpp: KLocalizedString offsetstr; int offset = 0; if ( alarm->hasStartOffset() ) { offset = alarm->startOffset().asSeconds(); if ( offset < 0 ) { offsetstr = ki18nc( "N days/hours/minutes before/after the start/end", "%1 before the start" ); offset = -offset; } else { offsetstr = ki18nc( "N days/hours/minutes before/after the start/end", "%1 after the start" ); } } else if ( alarm->hasEndOffset() ) { offset = alarm->endOffset().asSeconds(); if ( offset < 0 ) { offsetstr = ki18nc( "N days/hours/minutes before/after the start/end", "%1 before the end" ); offset = -offset; } else { offsetstr = ki18nc( "N days/hours/minutes before/after the start/end", "%1 after the end" ); } } offset = offset / 60; // make minutes int useoffset = offset; if ( offset % ( 24 * 60 ) == 0 && offset > 0 ) { // divides evenly into days? useoffset = offset / ( 24 * 60 ); offsetstr = offsetstr.subs( i18np( "1 day", "%1 days", useoffset ) ); } else if ( offset % 60 == 0 && offset > 0 ) { // divides evenly into hours? useoffset = offset / 60; offsetstr = offsetstr.subs( i18np( "1 hour", "%1 hours", useoffset ) ); } else { useoffset = offset; offsetstr = offsetstr.subs( i18np( "1 minute", "%1 minutes", useoffset ) ); } alarmStrings << offsetstr.toString(); } txt = alarmStrings.join( i18nc( "Spacer for the joined list of categories", ", " ) ); } h = qMax( printCaptionAndText( p, alarmBox, cap, txt, captionFont, textFont ), h ); } QRect organizerBox( timesBox.left() + padding(), h + padding(), timesBox.right() - padding(), lineHeight ); h = qMax( printCaptionAndText( p, organizerBox, i18n( "Organizer: " ), (*it)->organizer()->fullName(), captionFont, textFont ), h ); // Finally, draw the frame around the time information... timesBox.setBottom( qMax( timesBox.bottom(), h + padding() ) ); drawBox( p, BOX_BORDER_WIDTH, timesBox ); QRect locationBox( timesBox ); locationBox.setTop( timesBox.bottom() + padding() ); locationBox.setHeight( 0 ); int locationBottom = 0; if ( !isJournal ) { locationBottom = drawBoxWithCaption( p, locationBox, i18n( "Location: " ), (*it)->location(), /*sameLine=*/true, /*expand=*/true, captionFont, textFont ); } locationBox.setBottom( locationBottom ); // Now start constructing the boxes from the bottom: QRect footerBox( locationBox ); footerBox.setBottom( box.bottom() ); footerBox.setTop( footerBox.bottom() - lineHeight - 2 * padding() ); QRect categoriesBox( footerBox ); categoriesBox.setBottom( footerBox.top() ); categoriesBox.setTop( categoriesBox.bottom() - lineHeight - 2 * padding() ); QRect attendeesBox( box.left(), categoriesBox.top() - padding() - box.height() / 9, box.width(), box.height() / 9 ); QRect attachmentsBox( box.left(), attendeesBox.top() - padding() - box.height() / 9, box.width() * 3 / 4 - padding(), box.height() / 9 ); QRect optionsBox( isJournal ? box.left() : attachmentsBox.right() + padding(), attachmentsBox.top(), 0, 0 ); optionsBox.setRight( box.right() ); optionsBox.setBottom( attachmentsBox.bottom() ); QRect notesBox( optionsBox.left(), isJournal ? ( timesBox.bottom() + padding() ) : ( locationBox.bottom() + padding() ), optionsBox.width(), 0 ); notesBox.setBottom( optionsBox.top() - padding() ); QRect descriptionBox( notesBox ); descriptionBox.setLeft( box.left() ); descriptionBox.setRight( attachmentsBox.right() ); // Adjust boxes depending on the show options... if ( !mShowSubitemsNotes || isJournal ) { descriptionBox.setRight( box.right() ); } if ( !mShowAttachments || !mShowAttendees ) { descriptionBox.setBottom( attachmentsBox.bottom() ); optionsBox.setTop( attendeesBox.top() ); optionsBox.setBottom( attendeesBox.bottom() ); notesBox.setBottom( attachmentsBox.bottom() ); if ( mShowOptions ) { attendeesBox.setRight( attachmentsBox.right() ); } if ( !mShowAttachments && !mShowAttendees ) { if ( mShowSubitemsNotes ) { descriptionBox.setBottom( attendeesBox.bottom() ); } if ( !mShowOptions ) { descriptionBox.setBottom( attendeesBox.bottom() ); notesBox.setBottom( attendeesBox.bottom() ); } } } if ( mShowAttachments && !isJournal ) { if ( !mShowOptions ) { attachmentsBox.setRight( box.right() ); attachmentsBox.setRight( box.right() ); } if ( !mShowAttendees ) { attachmentsBox.setTop( attendeesBox.top() ); attachmentsBox.setBottom( attendeesBox.bottom() ); } } int newBottom = drawBoxWithCaption( p, descriptionBox, i18n( "Description:" ), (*it)->description(), /*sameLine=*/false, /*expand=*/false, captionFont, textFont, (*it)->descriptionIsRich() ); if ( mShowNoteLines ) { drawNoteLines( p, descriptionBox, newBottom ); } - Akonadi::Item item = mCalendar->item( (*it)->uid() ); + Akonadi::Item item = mCalendar->item( (*it) ); Akonadi::Item::List relations = mCalendar->childItems( item.id() ); if ( mShowSubitemsNotes && !isJournal ) { if ( relations.isEmpty() || (*it)->type() != KCalCore::Incidence::TypeTodo ) { int notesPosition = drawBoxWithCaption( p, notesBox, i18n( "Notes:" ), QString(), /*sameLine=*/false, /*expand=*/false, captionFont, textFont ); drawNoteLines( p, notesBox, notesPosition ); } else { QString subitemCaption; if ( relations.count() == 0 ) { subitemCaption = i18n( "No Subitems" ); txt.clear(); } else { subitemCaption = i18np( "1 Subitem:", "%1 Subitems:", relations.count() ); } QString subitemString; QString statusString; QString datesString; int count = 0; foreach ( const Akonadi::Item &item, relations ) { KCalCore::Todo::Ptr todo = CalendarSupport::todo( item ); ++count; if ( !todo ) { // defensive, skip any zero pointers continue; } // format the status statusString = KCalUtils::Stringify::incidenceStatus( todo->status() ); if ( statusString.isEmpty() ) { if ( todo->status() == KCalCore::Incidence::StatusNone ) { statusString = i18nc( "no status", "none" ); } else { statusString = i18nc( "unknown status", "unknown" ); } } // format the dates if provided datesString.clear(); KDateTime::Spec spec = KCalPrefs::instance()->timeSpec(); if ( todo->dtStart().isValid() ) { datesString += i18nc( "subitem start date", "Start Date: %1\n", KGlobal::locale()->formatDate( todo->dtStart().toTimeSpec( spec ).date(), KLocale::ShortDate ) ); if ( !todo->allDay() ) { datesString += i18nc( "subitem start time", "Start Time: %1\n", KGlobal::locale()->formatTime( todo->dtStart().toTimeSpec( spec ).time(), false, false ) ); } } if ( todo->dateTime( KCalCore::Incidence::RoleEnd ).isValid() ) { subitemString += i18nc( "subitem due date", "Due Date: %1\n", KGlobal::locale()->formatDate( todo->dateTime( KCalCore::Incidence::RoleEnd ).toTimeSpec( spec ).date(), KLocale::ShortDate ) ); if ( !todo->allDay() ) { subitemString += i18nc( "subitem due time", "Due Time: %1\n", KGlobal::locale()->formatTime( todo->dateTime( KCalCore::Incidence::RoleEnd ).toTimeSpec( spec ).time(), false, false ) ); } } subitemString += i18nc( "subitem counter", "%1: ", count ); subitemString += todo->summary(); subitemString += QLatin1Char('\n'); if ( !datesString.isEmpty() ) { subitemString += datesString; subitemString += QLatin1Char('\n'); } subitemString += i18nc( "subitem Status: statusString", "Status: %1\n", statusString ); subitemString += KCalUtils::IncidenceFormatter::recurrenceString( todo ) + QLatin1Char('\n'); subitemString += i18nc( "subitem Priority: N", "Priority: %1\n", todo->priority() ); subitemString += i18nc( "subitem Secrecy: secrecyString", "Secrecy: %1\n", KCalUtils::Stringify::incidenceSecrecy( todo->secrecy() ) ); subitemString += QLatin1Char('\n'); } drawBoxWithCaption( p, notesBox, subitemCaption, subitemString, /*sameLine=*/false, /*expand=*/false, captionFont, textFont ); } } if ( mShowAttachments && !isJournal ) { KCalCore::Attachment::List attachments = (*it)->attachments(); QString attachmentCaption; if ( attachments.count() == 0 ) { attachmentCaption = i18n( "No Attachments" ); txt.clear(); } else { attachmentCaption = i18np( "1 Attachment:", "%1 Attachments:", attachments.count() ); } QString attachmentString; KCalCore::Attachment::List::ConstIterator ait = attachments.constBegin(); for ( ; ait != attachments.constEnd(); ++ait ) { if ( !attachmentString.isEmpty() ) { attachmentString += i18nc( "Spacer for list of attachments", " " ); } attachmentString.append( (*ait)->label() ); } drawBoxWithCaption( p, attachmentsBox, attachmentCaption, attachmentString, /*sameLine=*/false, /*expand=*/false, captionFont, textFont ); } if ( mShowAttendees ) { KCalCore::Attendee::List attendees = (*it)->attendees(); QString attendeeCaption; if ( attendees.count() == 0 ) { attendeeCaption = i18n( "No Attendees" ); } else { attendeeCaption = i18np( "1 Attendee:", "%1 Attendees:", attendees.count() ); } QString attendeeString; KCalCore::Attendee::List::ConstIterator ait = attendees.constBegin(); for ( ; ait != attendees.constEnd(); ++ait ) { if ( !attendeeString.isEmpty() ) { attendeeString += QLatin1Char('\n'); } attendeeString += i18nc( "Formatting of an attendee: " "'Name (Role): Status', e.g. 'Reinhold Kainhofer " " (Participant): Awaiting Response'", "%1 (%2): %3", (*ait)->fullName(), KCalUtils::Stringify::attendeeRole( (*ait)->role() ), KCalUtils::Stringify::attendeeStatus( ( *ait )->status() ) ); } drawBoxWithCaption( p, attendeesBox, attendeeCaption, attendeeString, /*sameLine=*/false, /*expand=*/false, captionFont, textFont ); } if ( mShowOptions ) { QString optionsString; if ( !KCalUtils::Stringify::incidenceStatus( (*it)->status() ).isEmpty() ) { optionsString += i18n( "Status: %1", KCalUtils::Stringify::incidenceStatus( (*it)->status() ) ); optionsString += QLatin1Char('\n'); } if ( !KCalUtils::Stringify::incidenceSecrecy( (*it)->secrecy() ).isEmpty() ) { optionsString += i18n( "Secrecy: %1", KCalUtils::Stringify::incidenceSecrecy( (*it)->secrecy() ) ); optionsString += QLatin1Char('\n'); } if ( (*it)->type() == KCalCore::Incidence::TypeEvent ) { KCalCore::Event::Ptr e = (*it).staticCast(); if ( e->transparency() == KCalCore::Event::Opaque ) { optionsString += i18n( "Show as: Busy" ); } else { optionsString += i18n( "Show as: Free" ); } optionsString += QLatin1Char('\n'); } else if ( (*it)->type() == KCalCore::Incidence::TypeTodo ) { KCalCore::Todo::Ptr t = (*it).staticCast(); if ( t->isOverdue() ) { optionsString += i18n( "This task is overdue!" ); optionsString += QLatin1Char('\n'); } } else if ( (*it)->type() == KCalCore::Incidence::TypeJournal ) { //TODO: Anything Journal-specific? } drawBoxWithCaption( p, optionsBox, i18n( "Settings: " ), optionsString, /*sameLine=*/false, /*expand=*/false, captionFont, textFont ); } drawBoxWithCaption( p, categoriesBox, i18n( "Categories: " ), (*it)->categories().join( i18nc( "Spacer for the joined list of categories", ", " ) ), /*sameLine=*/true, /*expand=*/false, captionFont, textFont ); if ( mPrintFooter ) { drawFooter( p, footerBox ); } } p.setFont( oldFont ); } /************************************************************** * Print Day **************************************************************/ CalPrintDay::CalPrintDay() : CalPrintPluginBase() { } CalPrintDay::~CalPrintDay() { } QWidget *CalPrintDay::createConfigWidget( QWidget *w ) { return new CalPrintDayConfig( w ); } void CalPrintDay::readSettingsWidget() { CalPrintDayConfig *cfg = dynamic_cast( ( QWidget* )mConfigWidget ); if ( cfg ) { mFromDate = cfg->mFromDate->date(); mToDate = cfg->mToDate->date(); if ( cfg->mPrintTypeFilofax->isChecked() ) { mDayPrintType = Filofax; } else if ( cfg->mPrintTypeTimetable->isChecked() ) { mDayPrintType = Timetable; } else { mDayPrintType = SingleTimetable; } mStartTime = cfg->mFromTime->time(); mEndTime = cfg->mToTime->time(); mIncludeAllEvents = cfg->mIncludeAllEvents->isChecked(); mIncludeDescription = cfg->mIncludeDescription->isChecked(); mSingleLineLimit = cfg->mSingleLineLimit->isChecked(); mIncludeTodos = cfg->mIncludeTodos->isChecked(); mUseColors = cfg->mColors->isChecked(); mPrintFooter = cfg->mPrintFooter->isChecked(); mShowNoteLines = cfg->mShowNoteLines->isChecked(); mExcludeTime = cfg->mExcludeTime->isChecked(); mExcludeConfidential = cfg->mExcludeConfidential->isChecked(); mExcludePrivate = cfg->mExcludePrivate->isChecked(); } } void CalPrintDay::setSettingsWidget() { CalPrintDayConfig *cfg = dynamic_cast( ( QWidget* )mConfigWidget ); if ( cfg ) { cfg->mFromDate->setDate( mFromDate ); cfg->mToDate->setDate( mToDate ); cfg->mPrintTypeFilofax->setChecked( mDayPrintType == Filofax ); cfg->mPrintTypeTimetable->setChecked( mDayPrintType == Timetable ); cfg->mPrintTypeSingleTimetable->setChecked( mDayPrintType == SingleTimetable ); cfg->mFromTime->setTime( mStartTime ); cfg->mToTime->setTime( mEndTime ); cfg->mIncludeAllEvents->setChecked( mIncludeAllEvents ); cfg->mIncludeDescription->setChecked( mIncludeDescription ); cfg->mSingleLineLimit->setChecked( mSingleLineLimit ); cfg->mIncludeTodos->setChecked( mIncludeTodos ); cfg->mColors->setChecked( mUseColors ); cfg->mPrintFooter->setChecked( mPrintFooter ); cfg->mShowNoteLines->setChecked( mShowNoteLines ); cfg->mExcludeTime->setChecked( mExcludeTime ); cfg->mExcludeConfidential->setChecked( mExcludeConfidential ); cfg->mExcludePrivate->setChecked( mExcludePrivate ); } } void CalPrintDay::loadConfig() { if ( mConfig ) { KConfigGroup grp( mConfig, groupName() ); QDate dt = QDate::currentDate(); // any valid QDate will do QTime tm1( dayStart() ); QDateTime startTm( dt, tm1 ); QDateTime endTm( dt, tm1.addSecs( 12 * 60 * 60 ) ); mStartTime = grp.readEntry( "Start time", startTm ).time(); mEndTime = grp.readEntry( "End time", endTm ).time(); mIncludeDescription = grp.readEntry( "Include description", false ); mIncludeTodos = grp.readEntry( "Include todos", false ); mIncludeAllEvents = grp.readEntry( "Include all events", false ); mDayPrintType = (eDayPrintType)( grp.readEntry( "Print type", (int)Timetable ) ); mSingleLineLimit = grp.readEntry( "Single line limit", false ); mShowNoteLines = grp.readEntry( "Note Lines", false ); mExcludeTime = grp.readEntry( "Exclude time", false ); mExcludeConfidential = grp.readEntry( "Exclude confidential", true ); mExcludePrivate = grp.readEntry( "Exclude private", true ); } setSettingsWidget(); } void CalPrintDay::saveConfig() { readSettingsWidget(); if ( mConfig ) { KConfigGroup grp( mConfig, groupName() ); QDateTime dt = QDateTime::currentDateTime(); // any valid QDateTime will do dt.setTime( mStartTime ); grp.writeEntry( "Start time", dt ); dt.setTime( mEndTime ); grp.writeEntry( "End time", dt ); grp.writeEntry( "Include description", mIncludeDescription ); grp.writeEntry( "Include todos", mIncludeTodos ); grp.writeEntry( "Include all events", mIncludeAllEvents ); grp.writeEntry( "Print type", int( mDayPrintType ) ); grp.writeEntry( "Single line limit", mSingleLineLimit ); grp.writeEntry( "Note Lines", mShowNoteLines ); grp.writeEntry( "Exclude time", mExcludeTime ); grp.writeEntry( "Exclude confidential", mExcludeConfidential ); grp.writeEntry( "Exclude private", mExcludePrivate ); } } void CalPrintDay::setDateRange( const QDate &from, const QDate &to ) { CalPrintPluginBase::setDateRange( from, to ); CalPrintDayConfig *cfg = dynamic_cast( ( QWidget* )mConfigWidget ); if ( cfg ) { cfg->mFromDate->setDate( from ); cfg->mToDate->setDate( to ); } } void CalPrintDay::print( QPainter &p, int width, int height ) { QDate curDay( mFromDate ); KDateTime::Spec timeSpec = KSystemTimeZones::local(); QRect headerBox( 0, 0, width, headerHeight() ); QRect footerBox( 0, height - footerHeight(), width, footerHeight() ); height -= footerHeight(); KLocale *local = KGlobal::locale(); switch ( mDayPrintType ) { case Filofax: case SingleTimetable: { QRect daysBox( headerBox ); daysBox.setTop( headerBox.bottom() + padding() ); daysBox.setBottom( height ); QString line1 = local->formatDate( mFromDate ); QString line2 = local->formatDate( mToDate ); QString title; if ( orientation() == QPrinter::Landscape ) { title = i18nc( "date from-to", "%1 - %2", line1, line2 ); } else { title = i18nc( "date from-\nto", "%1 -\n%2", line1, line2 ); } drawHeader( p, title, mFromDate, QDate(), headerBox ); if ( mDayPrintType == Filofax ) { drawDays( p, mFromDate, mToDate, mStartTime, mEndTime, daysBox, mSingleLineLimit, mShowNoteLines, mIncludeDescription, mExcludeConfidential, mExcludePrivate ); } else if ( mDayPrintType == SingleTimetable ) { drawTimeTable( p, mFromDate, mToDate, mIncludeAllEvents, mStartTime, mEndTime, daysBox, mIncludeDescription, mExcludeTime, mExcludeConfidential, mExcludePrivate ); } if ( mPrintFooter ) { drawFooter( p, footerBox ); } } break; case Timetable: default: do { QTime curStartTime( mStartTime ); QTime curEndTime( mEndTime ); // For an invalid time range, simply show one hour, starting at the hour // before the given start time if ( curEndTime <= curStartTime ) { curStartTime = QTime( curStartTime.hour(), 0, 0 ); curEndTime = curStartTime.addSecs( 3600 ); } drawHeader( p, local->formatDate( curDay ), curDay, QDate(), headerBox ); KCalCore::Event::List eventList = mCalendar->events( curDay, timeSpec, KCalCore::EventSortStartDate, KCalCore::SortDirectionAscending ); // split out the all day events as they will be printed in a separate box KCalCore::Event::List alldayEvents, timedEvents; foreach( const KCalCore::Event::Ptr &event, eventList ) { if ( event->allDay() ) { alldayEvents.append( event ); } else { timedEvents.append( event ); } } int fontSize = 11; QFont textFont( QLatin1String("sans-serif"), fontSize, QFont::Normal ); p.setFont( textFont ); int lineSpacing = p.fontMetrics().lineSpacing(); int maxAllDayEvents = 8; // the max we allow to be printed, sorry. int allDayHeight = qMin( alldayEvents.count(), maxAllDayEvents ) * lineSpacing; allDayHeight = qMax( allDayHeight, ( 5 * lineSpacing ) ) + ( 2 * padding() ); QRect allDayBox( TIMELINE_WIDTH + padding(), headerBox.bottom() + padding(), width - TIMELINE_WIDTH - padding(), allDayHeight ); if ( alldayEvents.count() > 0 ) { // draw the side bar for all-day events QFont oldFont( p.font() ); p.setFont( QFont( QLatin1String("sans-serif"), 9, QFont::Normal ) ); drawVerticalBox( p, BOX_BORDER_WIDTH, QRect( 0, headerBox.bottom() + padding(), TIMELINE_WIDTH, allDayHeight ), i18n( "Today's Events" ), Qt::AlignHCenter | Qt::AlignVCenter | Qt::TextWordWrap ); p.setFont( oldFont ); // now draw at most maxAllDayEvents in the all-day box drawBox( p, BOX_BORDER_WIDTH, allDayBox ); QRect eventBox( allDayBox ); eventBox.setLeft( TIMELINE_WIDTH + ( 2 * padding() ) ); eventBox.setTop( eventBox.top() + padding() ); eventBox.setBottom( eventBox.top() + lineSpacing ); int count = 0; foreach ( const KCalCore::Event::Ptr &event, alldayEvents ) { if ( count == maxAllDayEvents ) { break; } count++; QString str; if ( event->location().isEmpty() ) { str = cleanStr( event->summary() ); } else { str = i18nc( "summary, location", "%1, %2", cleanStr( event->summary() ), cleanStr( event->location() ) ); } printEventString( p, eventBox, str ); eventBox.setTop( eventBox.bottom() ); eventBox.setBottom( eventBox.top() + lineSpacing ); } } else { allDayBox.setBottom( headerBox.bottom() ); } QRect dayBox( allDayBox ); dayBox.setTop( allDayBox.bottom() + padding() ); dayBox.setBottom( height ); QList workDays = CalendarSupport::workDays( curDay, curDay ); drawAgendaDayBox( p, timedEvents, curDay, mIncludeAllEvents, curStartTime, curEndTime, dayBox, mIncludeDescription, mExcludeTime, mExcludeConfidential, mExcludePrivate, workDays ); QRect tlBox( dayBox ); tlBox.setLeft( 0 ); tlBox.setWidth( TIMELINE_WIDTH ); drawTimeLine( p, curStartTime, curEndTime, tlBox ); if ( mPrintFooter ) { drawFooter( p, footerBox ); } curDay = curDay.addDays( 1 ); if ( curDay <= mToDate ) { mPrinter->newPage(); } } while ( curDay <= mToDate ); } //switch } /************************************************************** * Print Week **************************************************************/ CalPrintWeek::CalPrintWeek() : CalPrintPluginBase() { } CalPrintWeek::~CalPrintWeek() { } QWidget *CalPrintWeek::createConfigWidget( QWidget *w ) { return new CalPrintWeekConfig( w ); } void CalPrintWeek::readSettingsWidget() { CalPrintWeekConfig *cfg = dynamic_cast( ( QWidget* )mConfigWidget ); if ( cfg ) { mFromDate = cfg->mFromDate->date(); mToDate = cfg->mToDate->date(); if ( cfg->mPrintTypeFilofax->isChecked() ) { mWeekPrintType = Filofax; } else if ( cfg->mPrintTypeTimetable->isChecked() ) { mWeekPrintType = Timetable; } else if ( cfg->mPrintTypeSplitWeek->isChecked() ) { mWeekPrintType = SplitWeek; } else { mWeekPrintType = Timetable; } mStartTime = cfg->mFromTime->time(); mEndTime = cfg->mToTime->time(); mShowNoteLines = cfg->mShowNoteLines->isChecked(); mSingleLineLimit = cfg->mSingleLineLimit->isChecked(); mIncludeTodos = cfg->mIncludeTodos->isChecked(); mUseColors = cfg->mColors->isChecked(); mPrintFooter = cfg->mPrintFooter->isChecked(); mIncludeDescription = cfg->mIncludeDescription->isChecked(); mExcludeTime = cfg->mExcludeTime->isChecked(); mExcludeConfidential = cfg->mExcludeConfidential->isChecked(); mExcludePrivate = cfg->mExcludePrivate->isChecked(); } } void CalPrintWeek::setSettingsWidget() { CalPrintWeekConfig *cfg = dynamic_cast( ( QWidget* )mConfigWidget ); if ( cfg ) { cfg->mFromDate->setDate( mFromDate ); cfg->mToDate->setDate( mToDate ); cfg->mPrintTypeFilofax->setChecked( mWeekPrintType == Filofax ); cfg->mPrintTypeTimetable->setChecked( mWeekPrintType == Timetable ); cfg->mPrintTypeSplitWeek->setChecked( mWeekPrintType == SplitWeek ); cfg->mFromTime->setTime( mStartTime ); cfg->mToTime->setTime( mEndTime ); cfg->mShowNoteLines->setChecked( mShowNoteLines ); cfg->mSingleLineLimit->setChecked( mSingleLineLimit ); cfg->mIncludeTodos->setChecked( mIncludeTodos ); cfg->mColors->setChecked( mUseColors ); cfg->mPrintFooter->setChecked( mPrintFooter ); cfg->mIncludeDescription->setChecked( mIncludeDescription ); cfg->mExcludeTime->setChecked( mExcludeTime ); cfg->mExcludeConfidential->setChecked( mExcludeConfidential ); cfg->mExcludePrivate->setChecked( mExcludePrivate ); } } void CalPrintWeek::loadConfig() { if ( mConfig ) { KConfigGroup grp( mConfig, groupName() ); QDate dt = QDate::currentDate(); // any valid QDate will do QTime tm1( dayStart() ); QDateTime startTm( dt, tm1 ); QDateTime endTm( dt, tm1.addSecs( 43200 ) ); mStartTime = grp.readEntry( "Start time", startTm ).time(); mEndTime = grp.readEntry( "End time", endTm ).time(); mShowNoteLines = grp.readEntry( "Note Lines", false ); mSingleLineLimit = grp.readEntry( "Single line limit", false ); mIncludeTodos = grp.readEntry( "Include todos", false ); mWeekPrintType = (eWeekPrintType)( grp.readEntry( "Print type", (int)Filofax ) ); mIncludeDescription = grp.readEntry( "Include Description", false ); mExcludeTime = grp.readEntry( "Exclude Time", false ); mExcludeConfidential = grp.readEntry( "Exclude confidential", true ); mExcludePrivate = grp.readEntry( "Exclude private", true ); } setSettingsWidget(); } void CalPrintWeek::saveConfig() { readSettingsWidget(); if ( mConfig ) { KConfigGroup grp( mConfig, groupName() ); QDateTime dt = QDateTime::currentDateTime(); // any valid QDateTime will do dt.setTime( mStartTime ); grp.writeEntry( "Start time", dt ); dt.setTime( mEndTime ); grp.writeEntry( "End time", dt ); grp.writeEntry( "Note Lines", mShowNoteLines ); grp.writeEntry( "Single line limit", mSingleLineLimit ); grp.writeEntry( "Include todos", mIncludeTodos ); grp.writeEntry( "Print type", int( mWeekPrintType ) ); grp.writeEntry( "Include Description", mIncludeDescription ); grp.writeEntry( "Exclude Time", mExcludeTime ); grp.writeEntry( "Exclude confidential", mExcludeConfidential ); grp.writeEntry( "Exclude private", mExcludePrivate ); } } QPrinter::Orientation CalPrintWeek::defaultOrientation() { if ( mWeekPrintType == Filofax ) { return QPrinter::Portrait; } else if ( mWeekPrintType == SplitWeek ) { return QPrinter::Portrait; } else { return QPrinter::Landscape; } } void CalPrintWeek::setDateRange( const QDate &from, const QDate &to ) { CalPrintPluginBase::setDateRange( from, to ); CalPrintWeekConfig *cfg = dynamic_cast( ( QWidget* )mConfigWidget ); if ( cfg ) { cfg->mFromDate->setDate( from ); cfg->mToDate->setDate( to ); } } void CalPrintWeek::print( QPainter &p, int width, int height ) { QDate curWeek, fromWeek, toWeek; // correct begin and end to first and last day of week int weekdayCol = weekdayColumn( mFromDate.dayOfWeek() ); fromWeek = mFromDate.addDays( -weekdayCol ); weekdayCol = weekdayColumn( mToDate.dayOfWeek() ); toWeek = mToDate.addDays( 6 - weekdayCol ); curWeek = fromWeek.addDays( 6 ); KLocale *local = KGlobal::locale(); QString line1, line2, title; QRect headerBox( 0, 0, width, headerHeight() ); QRect footerBox( 0, height - footerHeight(), width, footerHeight() ); height -= footerHeight(); QRect weekBox( headerBox ); weekBox.setTop( headerBox.bottom() + padding() ); weekBox.setBottom( height ); switch ( mWeekPrintType ) { case Filofax: do { line1 = local->formatDate( curWeek.addDays( -6 ) ); line2 = local->formatDate( curWeek ); if ( orientation() == QPrinter::Landscape ) { title = i18nc( "date from-to", "%1 - %2", line1, line2 ); } else { title = i18nc( "date from-\nto", "%1 -\n%2", line1, line2 ); } drawHeader( p, title, curWeek.addDays( -6 ), QDate(), headerBox ); drawWeek( p, curWeek, mStartTime, mEndTime, weekBox, mSingleLineLimit, mShowNoteLines, mIncludeDescription, mExcludeConfidential, mExcludePrivate ); if ( mPrintFooter ) { drawFooter( p, footerBox ); } curWeek = curWeek.addDays( 7 ); if ( curWeek <= toWeek ) { mPrinter->newPage(); } } while ( curWeek <= toWeek ); break; case Timetable: default: do { line1 = local->formatDate( curWeek.addDays( -6 ) ); line2 = local->formatDate( curWeek ); if ( orientation() == QPrinter::Landscape ) { title = i18nc( "date from - to (week number)", "%1 - %2 (Week %3)", line1, line2, curWeek.weekNumber() ); } else { title = i18nc( "date from -\nto (week number)", "%1 -\n%2 (Week %3)", line1, line2, curWeek.weekNumber() ); } drawHeader( p, title, curWeek, QDate(), headerBox ); drawTimeTable( p, fromWeek, curWeek, false, mStartTime, mEndTime, weekBox, mIncludeDescription, mExcludeTime, mExcludeConfidential, mExcludePrivate ); if ( mPrintFooter ) { drawFooter( p, footerBox ); } fromWeek = fromWeek.addDays( 7 ); curWeek = fromWeek.addDays( 6 ); if ( curWeek <= toWeek ) { mPrinter->newPage(); } } while ( curWeek <= toWeek ); break; case SplitWeek: { QRect weekBox1( weekBox ); // On the left side there are four days (mo-th) plus the timeline, // on the right there are only three days (fr-su) plus the timeline. Don't // use the whole width, but rather give them the same width as on the left. weekBox1.setRight( int( ( width - TIMELINE_WIDTH ) * 3. / 4. + TIMELINE_WIDTH ) ); do { QDate endLeft( fromWeek.addDays( 3 ) ); int hh = headerHeight(); drawSplitHeaderRight( p, fromWeek, curWeek, QDate(), width, hh ); drawTimeTable( p, fromWeek, endLeft, false, mStartTime, mEndTime, weekBox, mIncludeDescription, mExcludeTime, mExcludeConfidential, mExcludePrivate ); if ( mPrintFooter ) { drawFooter( p, weekBox1 ); } mPrinter->newPage(); drawSplitHeaderRight( p, fromWeek, curWeek, QDate(), width, hh ); drawTimeTable( p, endLeft.addDays( 1 ), curWeek, false, mStartTime, mEndTime, weekBox1, mIncludeDescription, mExcludeTime, mExcludeConfidential, mExcludePrivate ); if ( mPrintFooter ) { drawFooter( p, footerBox ); } fromWeek = fromWeek.addDays( 7 ); curWeek = fromWeek.addDays( 6 ); if ( curWeek <= toWeek ) { mPrinter->newPage(); } } while ( curWeek <= toWeek ); } break; } } /************************************************************** * Print Month **************************************************************/ CalPrintMonth::CalPrintMonth() : CalPrintPluginBase() { } CalPrintMonth::~CalPrintMonth() { } QWidget *CalPrintMonth::createConfigWidget( QWidget *w ) { return new CalPrintMonthConfig( w ); } void CalPrintMonth::readSettingsWidget() { CalPrintMonthConfig *cfg = dynamic_cast( ( QWidget* )mConfigWidget ); if ( cfg ) { mFromDate = QDate( cfg->mFromYear->value(), cfg->mFromMonth->currentIndex()+1, 1 ); mToDate = QDate( cfg->mToYear->value(), cfg->mToMonth->currentIndex()+1, 1 ); mWeekNumbers = cfg->mWeekNumbers->isChecked(); mRecurDaily = cfg->mRecurDaily->isChecked(); mRecurWeekly = cfg->mRecurWeekly->isChecked(); mIncludeTodos = cfg->mIncludeTodos->isChecked(); mShowNoteLines = cfg->mShowNoteLines->isChecked(); mSingleLineLimit = cfg->mSingleLineLimit->isChecked(); mUseColors = cfg->mColors->isChecked(); mPrintFooter = cfg->mPrintFooter->isChecked(); mIncludeDescription = cfg->mIncludeDescription->isChecked(); mExcludeConfidential = cfg->mExcludeConfidential->isChecked(); mExcludePrivate = cfg->mExcludePrivate->isChecked(); } } void CalPrintMonth::setSettingsWidget() { CalPrintMonthConfig *cfg = dynamic_cast( ( QWidget* )mConfigWidget ); if ( cfg ) { setDateRange( mFromDate, mToDate ); cfg->mWeekNumbers->setChecked( mWeekNumbers ); cfg->mRecurDaily->setChecked( mRecurDaily ); cfg->mRecurWeekly->setChecked( mRecurWeekly ); cfg->mIncludeTodos->setChecked( mIncludeTodos ); cfg->mShowNoteLines->setChecked( mShowNoteLines ); cfg->mSingleLineLimit->setChecked( mSingleLineLimit ); cfg->mColors->setChecked( mUseColors ); cfg->mPrintFooter->setChecked( mPrintFooter ); cfg->mIncludeDescription->setChecked( mIncludeDescription ); cfg->mExcludeConfidential->setChecked( mExcludeConfidential ); cfg->mExcludePrivate->setChecked( mExcludePrivate ); } } void CalPrintMonth::loadConfig() { if ( mConfig ) { KConfigGroup grp( mConfig, groupName() ); mWeekNumbers = grp.readEntry( "Print week numbers", true ); mRecurDaily = grp.readEntry( "Print daily incidences", true ); mRecurWeekly = grp.readEntry( "Print weekly incidences", true ); mIncludeTodos = grp.readEntry( "Include todos", false ); mSingleLineLimit = grp.readEntry( "Single line limit", false ); mShowNoteLines = grp.readEntry( "Note Lines", false ); mIncludeDescription = grp.readEntry( "Include description", false ); mExcludeConfidential = grp.readEntry( "Exclude confidential", true ); mExcludePrivate = grp.readEntry( "Exclude private", true ); } setSettingsWidget(); } void CalPrintMonth::saveConfig() { readSettingsWidget(); if ( mConfig ) { KConfigGroup grp( mConfig, groupName() ); grp.writeEntry( "Print week numbers", mWeekNumbers ); grp.writeEntry( "Print daily incidences", mRecurDaily ); grp.writeEntry( "Print weekly incidences", mRecurWeekly ); grp.writeEntry( "Include todos", mIncludeTodos ); grp.writeEntry( "Single line limit", mSingleLineLimit ); grp.writeEntry( "Note Lines", mShowNoteLines ); grp.writeEntry( "Include description", mIncludeDescription ); grp.writeEntry( "Exclude confidential", mExcludeConfidential ); grp.writeEntry( "Exclude private", mExcludePrivate ); } } void CalPrintMonth::setDateRange( const QDate &from, const QDate &to ) { CalPrintPluginBase::setDateRange( from, to ); CalPrintMonthConfig *cfg = dynamic_cast( ( QWidget* )mConfigWidget ); const KCalendarSystem *calSys = calendarSystem(); if ( cfg && calSys ) { cfg->mFromMonth->clear(); for ( int i=0; imonthsInYear( mFromDate ); ++i ) { cfg->mFromMonth->addItem( calSys->monthName( i+1, mFromDate.year() ) ); } cfg->mToMonth->clear(); for ( int i=0; imonthsInYear( mToDate ); ++i ) { cfg->mToMonth->addItem( calSys->monthName( i+1, mToDate.year() ) ); } } if ( cfg ) { cfg->mFromMonth->setCurrentIndex( from.month()-1 ); cfg->mFromYear->setValue( to.year() ); cfg->mToMonth->setCurrentIndex( mToDate.month()-1 ); cfg->mToYear->setValue( mToDate.year() ); } } void CalPrintMonth::print( QPainter &p, int width, int height ) { QDate curMonth, fromMonth, toMonth; fromMonth = mFromDate.addDays( -( mFromDate.day() - 1 ) ); toMonth = mToDate.addDays( mToDate.daysInMonth() - mToDate.day() ); curMonth = fromMonth; const KCalendarSystem *calSys = calendarSystem(); if ( !calSys ) { return; } QRect headerBox( 0, 0, width, headerHeight() ); QRect footerBox( 0, height - footerHeight(), width, footerHeight() ); height -= footerHeight(); QRect monthBox( 0, 0, width, height ); monthBox.setTop( headerBox.bottom() + padding() ); do { QString title( i18nc( "monthname year", "%1 %2", calSys->monthName( curMonth ), curMonth.year() ) ); QDate tmp( fromMonth ); int weekdayCol = weekdayColumn( tmp.dayOfWeek() ); tmp = tmp.addDays( -weekdayCol ); drawHeader( p, title, curMonth.addMonths( -1 ), curMonth.addMonths( 1 ), headerBox ); drawMonthTable( p, curMonth, QTime(), QTime(), mWeekNumbers, mRecurDaily, mRecurWeekly, mSingleLineLimit, mShowNoteLines, mIncludeDescription, mExcludeConfidential, mExcludePrivate, monthBox ); if ( mPrintFooter ) { drawFooter( p, footerBox ); } curMonth = curMonth.addDays( curMonth.daysInMonth() ); if ( curMonth <= toMonth ) { mPrinter->newPage(); } } while ( curMonth <= toMonth ); } /************************************************************** * Print Todos **************************************************************/ CalPrintTodos::CalPrintTodos() : CalPrintPluginBase() { mTodoSortField = TodoFieldUnset; mTodoSortDirection = TodoDirectionUnset; } CalPrintTodos::~CalPrintTodos() { } QWidget *CalPrintTodos::createConfigWidget( QWidget *w ) { return new CalPrintTodoConfig( w ); } void CalPrintTodos::readSettingsWidget() { CalPrintTodoConfig *cfg = dynamic_cast( ( QWidget* )mConfigWidget ); if ( cfg ) { mPageTitle = cfg->mTitle->text(); if ( cfg->mPrintAll->isChecked() ) { mTodoPrintType = TodosAll; } else if ( cfg->mPrintUnfinished->isChecked() ) { mTodoPrintType = TodosUnfinished; } else if ( cfg->mPrintDueRange->isChecked() ) { mTodoPrintType = TodosDueRange; } else { mTodoPrintType = TodosAll; } mFromDate = cfg->mFromDate->date(); mToDate = cfg->mToDate->date(); mIncludeDescription = cfg->mDescription->isChecked(); mIncludePriority = cfg->mPriority->isChecked(); mIncludeDueDate = cfg->mDueDate->isChecked(); mIncludePercentComplete = cfg->mPercentComplete->isChecked(); mConnectSubTodos = cfg->mConnectSubTodos->isChecked(); mStrikeOutCompleted = cfg->mStrikeOutCompleted->isChecked(); mExcludeConfidential = cfg->mExcludeConfidential->isChecked(); mExcludePrivate = cfg->mExcludePrivate->isChecked(); mTodoSortField = (eTodoSortField)cfg->mSortField->currentIndex(); mTodoSortDirection = (eTodoSortDirection)cfg->mSortDirection->currentIndex(); mPrintFooter = cfg->mPrintFooter->isChecked(); } } void CalPrintTodos::setSettingsWidget() { CalPrintTodoConfig *cfg = dynamic_cast( ( QWidget* )mConfigWidget ); if ( cfg ) { cfg->mTitle->setText( mPageTitle ); cfg->mPrintAll->setChecked( mTodoPrintType == TodosAll ); cfg->mPrintUnfinished->setChecked( mTodoPrintType == TodosUnfinished ); cfg->mPrintDueRange->setChecked( mTodoPrintType == TodosDueRange ); cfg->mFromDate->setDate( mFromDate ); cfg->mToDate->setDate( mToDate ); cfg->mDescription->setChecked( mIncludeDescription ); cfg->mPriority->setChecked( mIncludePriority ); cfg->mDueDate->setChecked( mIncludeDueDate ); cfg->mPercentComplete->setChecked( mIncludePercentComplete ); cfg->mConnectSubTodos->setChecked( mConnectSubTodos ); cfg->mStrikeOutCompleted->setChecked( mStrikeOutCompleted ); cfg->mExcludeConfidential->setChecked( mExcludeConfidential ); cfg->mExcludePrivate->setChecked( mExcludePrivate ); if ( mTodoSortField != TodoFieldUnset ) { // do not insert if already done so. cfg->mSortField->addItem( i18nc( "@option sort by title", "Title" ) ); cfg->mSortField->addItem( i18nc( "@option sort by start date/time", "Start Date" ) ); cfg->mSortField->addItem( i18nc( "@option sort by due date/time", "Due Date" ) ); cfg->mSortField->addItem( i18nc( "@option sort by priority", "Priority" ) ); cfg->mSortField->addItem( i18nc( "@option sort by percent completed", "Percent Complete" ) ); cfg->mSortField->setCurrentIndex( mTodoSortField ); } if ( mTodoSortDirection != TodoDirectionUnset ) { // do not insert if already done so. cfg->mSortDirection->addItem( i18nc( "@option sort in increasing order", "Ascending" ) ); cfg->mSortDirection->addItem( i18nc( "@option sort in descreasing order", "Descending" ) ); cfg->mSortDirection->setCurrentIndex( mTodoSortDirection ); } cfg->mPrintFooter->setChecked( mPrintFooter ); } } void CalPrintTodos::loadConfig() { if ( mConfig ) { KConfigGroup grp( mConfig, groupName() ); mPageTitle = grp.readEntry( "Page title", i18n( "To-do list" ) ); mTodoPrintType = (eTodoPrintType)grp.readEntry( "Print type", (int)TodosAll ); mIncludeDescription = grp.readEntry( "Include description", true ); mIncludePriority = grp.readEntry( "Include priority", true ); mIncludeDueDate = grp.readEntry( "Include due date", true ); mIncludePercentComplete = grp.readEntry( "Include percentage completed", true ); mConnectSubTodos = grp.readEntry( "Connect subtodos", true ); mStrikeOutCompleted = grp.readEntry( "Strike out completed summaries", true ); mTodoSortField = (eTodoSortField)grp.readEntry( "Sort field", (int)TodoFieldSummary ); mTodoSortDirection = (eTodoSortDirection)grp.readEntry( "Sort direction", (int)TodoDirectionAscending ); mExcludeConfidential = grp.readEntry( "Exclude confidential", true ); mExcludePrivate = grp.readEntry( "Exclude private", true ); } setSettingsWidget(); } void CalPrintTodos::saveConfig() { readSettingsWidget(); if ( mConfig ) { KConfigGroup grp( mConfig, groupName()); grp.writeEntry( "Page title", mPageTitle ); grp.writeEntry( "Print type", int( mTodoPrintType ) ); grp.writeEntry( "Include description", mIncludeDescription ); grp.writeEntry( "Include priority", mIncludePriority ); grp.writeEntry( "Include due date", mIncludeDueDate ); grp.writeEntry( "Include percentage completed", mIncludePercentComplete ); grp.writeEntry( "Connect subtodos", mConnectSubTodos ); grp.writeEntry( "Strike out completed summaries", mStrikeOutCompleted ); grp.writeEntry( "Sort field", (int)mTodoSortField ); grp.writeEntry( "Sort direction", (int)mTodoSortDirection ); grp.writeEntry( "Exclude confidential", mExcludeConfidential ); grp.writeEntry( "Exclude private", mExcludePrivate ); } } void CalPrintTodos::print( QPainter &p, int width, int height ) { // TODO: Find a good way to guarantee a nicely designed output int pospriority = 0; int possummary = 100; int posdue = width - 65; int poscomplete = posdue - 70; //Complete column is to right of the Due column int lineSpacing = 15; //int fontHeight = 10; QRect headerBox( 0, 0, width, headerHeight() ); QRect footerBox( 0, height - footerHeight(), width, footerHeight() ); height -= footerHeight(); // Draw the First Page Header drawHeader( p, mPageTitle, mFromDate, QDate(), headerBox ); // Draw the Column Headers int mCurrentLinePos = headerHeight() + 5; QString outStr; QFont oldFont( p.font() ); p.setFont( QFont( QLatin1String("sans-serif"), 9, QFont::Bold ) ); lineSpacing = p.fontMetrics().lineSpacing(); mCurrentLinePos += lineSpacing; if ( mIncludePriority ) { outStr += i18n( "Priority" ); p.drawText( pospriority, mCurrentLinePos - 2, outStr ); } else { pospriority = -1; } outStr.truncate( 0 ); outStr += i18nc( "@label to-do summary", "Title" ); p.drawText( possummary, mCurrentLinePos - 2, outStr ); if ( mIncludePercentComplete ) { if ( !mIncludeDueDate ) { //move Complete column to the right poscomplete = posdue; //if not print the Due Date column } outStr.truncate( 0 ); outStr += i18nc( "@label to-do percentage complete", "Complete" ); p.drawText( poscomplete, mCurrentLinePos - 2, outStr ); } else { poscomplete = -1; } if ( mIncludeDueDate ) { outStr.truncate( 0 ); outStr += i18nc( "@label to-do due date", "Due" ); p.drawText( posdue, mCurrentLinePos - 2, outStr ); } else { posdue = -1; } p.setFont( QFont( QLatin1String("sans-serif"), 10 ) ); //fontHeight = p.fontMetrics().height(); KCalCore::Todo::List todoList; KCalCore::Todo::List tempList; KCalCore::SortDirection sortDirection = KCalCore::SortDirectionAscending; switch( mTodoSortDirection ) { case TodoDirectionAscending: sortDirection = KCalCore::SortDirectionAscending; break; case TodoDirectionDescending: sortDirection = KCalCore::SortDirectionDescending; break; case TodoDirectionUnset: break; } KCalCore::TodoSortField sortField = KCalCore::TodoSortSummary; switch( mTodoSortField ) { case TodoFieldSummary: sortField = KCalCore::TodoSortSummary; break; case TodoFieldStartDate: sortField = KCalCore::TodoSortStartDate; break; case TodoFieldDueDate: sortField = KCalCore::TodoSortDueDate; break; case TodoFieldPriority: sortField = KCalCore::TodoSortPriority; break; case TodoFieldPercentComplete: sortField = KCalCore::TodoSortPercentComplete; break; case TodoFieldUnset: break; } // Create list of to-dos which will be printed todoList = mCalendar->todos( sortField, sortDirection ); switch( mTodoPrintType ) { case TodosAll: break; case TodosUnfinished: foreach( const KCalCore::Todo::Ptr& todo, todoList ) { Q_ASSERT( todo ); if ( !todo->isCompleted() ) { tempList.append( todo ); } } todoList = tempList; break; case TodosDueRange: foreach( const KCalCore::Todo::Ptr& todo, todoList ) { Q_ASSERT( todo ); if ( todo->hasDueDate() ) { if ( todo->dtDue().date() >= mFromDate && todo->dtDue().date() <= mToDate ) { tempList.append( todo ); } } else { tempList.append( todo ); } } todoList = tempList; break; } // Print to-dos int count = 0; foreach( const KCalCore::Todo::Ptr& todo, todoList ) { if ( ( mExcludeConfidential && todo->secrecy() == KCalCore::Incidence::SecrecyConfidential ) || ( mExcludePrivate && todo->secrecy() == KCalCore::Incidence::SecrecyPrivate ) ) { continue; } // Skip sub-to-dos. They will be printed recursively in drawTodo() if ( todo->relatedTo().isEmpty() ) { //review(AKONADI_PORT) count++; drawTodo( count, todo, p, sortField, sortDirection, mConnectSubTodos, mStrikeOutCompleted, mIncludeDescription, pospriority, possummary, posdue, poscomplete, 0, 0, mCurrentLinePos, width, height, todoList, 0, mExcludeConfidential, mExcludePrivate ); } } if ( mPrintFooter ) { drawFooter( p, footerBox ); } p.setFont( oldFont ); } diff --git a/calendarsupport/printing/calprintpluginbase.cpp b/calendarsupport/printing/calprintpluginbase.cpp index 7bec4e67b6..d2f429b54b 100644 --- a/calendarsupport/printing/calprintpluginbase.cpp +++ b/calendarsupport/printing/calprintpluginbase.cpp @@ -1,2238 +1,2238 @@ /* Copyright (c) 1998 Preston Brown Copyright (C) 2003 Reinhold Kainhofer Copyright (C) 2008 Ron Goodheart Copyright (c) 2012-2013 Allen Winter This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #include "calprintpluginbase.h" #include "cellitem.h" #include "kcalprefs.h" #include "utils.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // qCeil krazy:exclude=camelcase since no QMath using namespace CalendarSupport; static QString cleanStr( const QString &instr ) { QString ret = instr; return ret.replace( QLatin1Char('\n'), QLatin1Char(' ') ); } /****************************************************************** ** The Todo positioning structure ** ******************************************************************/ class CalPrintPluginBase::TodoParentStart { public: TodoParentStart( const QRect &pt = QRect(), bool hasLine = false, bool page = true ) : mRect( pt ), mHasLine( hasLine ), mSamePage( page ) {} QRect mRect; bool mHasLine; bool mSamePage; }; /****************************************************************** ** The Print item ** ******************************************************************/ class PrintCellItem : public CellItem { public: PrintCellItem( const KCalCore::Event::Ptr &event, const KDateTime &start, const KDateTime &end ) : mEvent( event ), mStart( start ), mEnd( end ) { } KCalCore::Event::Ptr event() const { return mEvent; } QString label() const { return mEvent->summary(); } KDateTime start() const { return mStart; } KDateTime end() const { return mEnd; } /** Calculate the start and end date/time of the recurrence that happens on the given day */ bool overlaps( CellItem *o ) const { PrintCellItem *other = static_cast( o ); return !( other->start() >= end() || other->end() <= start() ); } private: KCalCore::Event::Ptr mEvent; KDateTime mStart, mEnd; }; /****************************************************************** ** The Print plugin ** ******************************************************************/ CalPrintPluginBase::CalPrintPluginBase() : PrintPlugin(), mUseColors( true ), mPrintFooter( true ), mHeaderHeight( -1 ), mSubHeaderHeight( SUBHEADER_HEIGHT ), mFooterHeight( -1 ), mMargin( MARGIN_SIZE ), mPadding( PADDING_SIZE ), mCalSys( 0 ) { } CalPrintPluginBase::~CalPrintPluginBase() { } QWidget *CalPrintPluginBase::createConfigWidget( QWidget *w ) { QFrame *wdg = new QFrame( w ); QVBoxLayout *layout = new QVBoxLayout( wdg ); QLabel *title = new QLabel( description(), wdg ); QFont titleFont( title->font() ); titleFont.setPointSize( 20 ); titleFont.setBold( true ); title->setFont( titleFont ); layout->addWidget( title ); layout->addWidget( new QLabel( info(), wdg ) ); layout->addSpacing( 20 ); layout->addWidget( new QLabel( i18n( "This printing style does not have any configuration options." ), wdg ) ); layout->addStretch(); return wdg; } void CalPrintPluginBase::doPrint( QPrinter *printer ) { if ( !printer ) { return; } mPrinter = printer; QPainter p; mPrinter->setColorMode( mUseColors ? QPrinter::Color : QPrinter::GrayScale ); p.begin( mPrinter ); // TODO: Fix the margins!!! // the painter initially begins at 72 dpi per the Qt docs. // we want half-inch margins. int margins = margin(); p.setViewport( margins, margins, p.viewport().width() - 2 * margins, p.viewport().height() - 2 * margins ); // QRect vp( p.viewport() ); // vp.setRight( vp.right()*2 ); // vp.setBottom( vp.bottom()*2 ); // p.setWindow( vp ); int pageWidth = p.window().width(); int pageHeight = p.window().height(); // int pageWidth = p.viewport().width(); // int pageHeight = p.viewport().height(); print( p, pageWidth, pageHeight ); p.end(); mPrinter = 0; } void CalPrintPluginBase::doLoadConfig() { if ( mConfig ) { KConfigGroup group( mConfig, groupName() ); mConfig->sync(); QDateTime dt = QDateTime::currentDateTime(); mFromDate = group.readEntry( "FromDate", dt ).date(); mToDate = group.readEntry( "ToDate", dt ).date(); mUseColors = group.readEntry( "UseColors", true ); mPrintFooter = group.readEntry( "PrintFooter", true ); loadConfig(); } else { kDebug() << "No config available in loadConfig!!!!"; } } void CalPrintPluginBase::doSaveConfig() { if ( mConfig ) { KConfigGroup group( mConfig, groupName() ); saveConfig(); QDateTime dt = QDateTime::currentDateTime(); // any valid QDateTime will do dt.setDate( mFromDate ); group.writeEntry( "FromDate", dt ); dt.setDate( mToDate ); group.writeEntry( "ToDate", dt ); group.writeEntry( "UseColors", mUseColors ); group.writeEntry( "PrintFooter", mPrintFooter ); mConfig->sync(); } else { kDebug() << "No config available in saveConfig!!!!"; } } bool CalPrintPluginBase::useColors() const { return mUseColors; } void CalPrintPluginBase::setUseColors( bool useColors ) { mUseColors = useColors; } bool CalPrintPluginBase::printFooter() const { return mPrintFooter; } void CalPrintPluginBase::setPrintFooter( bool printFooter ) { mPrintFooter = printFooter; } QPrinter::Orientation CalPrintPluginBase::orientation() const { return mPrinter ? mPrinter->orientation() : QPrinter::Portrait; } QColor CalPrintPluginBase::getTextColor( const QColor &c ) const { double luminance = ( c.red() * 0.299 ) + ( c.green() * 0.587 ) + ( c.blue() * 0.114 ); return ( luminance > 128.0 ) ? QColor( 0, 0, 0 ) : QColor( 255, 255, 255 ); } QTime CalPrintPluginBase::dayStart() const { QTime start( 8, 0, 0 ); QDateTime dayBegins = KCalPrefs::instance()->dayBegins(); if ( dayBegins.isValid() ) { start = dayBegins.time(); } return start; } void CalPrintPluginBase::setColorsByIncidenceCategory( QPainter &p, const KCalCore::Incidence::Ptr &incidence ) const { QColor bgColor = categoryBgColor( incidence ); if ( bgColor.isValid() ) { p.setBrush( bgColor ); } QColor tColor( getTextColor( bgColor ) ); if ( tColor.isValid() ) { p.setPen( tColor ); } } QColor CalPrintPluginBase::categoryColor( const QStringList &categories ) const { if ( categories.isEmpty() ) { return KCalPrefs::instance()->unsetCategoryColor(); } // FIXME: Correctly treat events with multiple categories const QString cat = categories.first(); QColor bgColor; if ( cat.isEmpty() ) { bgColor = KCalPrefs::instance()->unsetCategoryColor(); } else { bgColor = KCalPrefs::instance()->categoryColor( cat ); } return bgColor; } QColor CalPrintPluginBase::categoryBgColor( const KCalCore::Incidence::Ptr &incidence ) const { if ( incidence ) { QColor backColor = categoryColor( incidence->categories() ); if ( incidence->type() == KCalCore::Incidence::TypeTodo ) { if ( ( incidence.staticCast() )->isOverdue() ) { backColor = QColor( 255, 100, 100 ); //was KOPrefs::instance()->todoOverdueColor(); } } return backColor; } else { return QColor(); } } QString CalPrintPluginBase::holidayString( const QDate &date ) const { QStringList lst = holiday( date ); return lst.join( i18nc( "@item:intext delimiter for joining holiday names", "," ) ); } KCalCore::Event::Ptr CalPrintPluginBase::holidayEvent( const QDate &date ) const { QString hstring( holidayString( date ) ); if ( hstring.isEmpty() ) { return KCalCore::Event::Ptr(); } KCalCore::Event::Ptr holiday( new KCalCore::Event ); holiday->setSummary( hstring ); holiday->setCategories( i18n( "Holiday" ) ); KDateTime kdt( date, QTime(), KSystemTimeZones::local() ); holiday->setDtStart( kdt ); holiday->setDtEnd( kdt ); holiday->setAllDay( true ); return holiday; } const KCalendarSystem *CalPrintPluginBase::calendarSystem() { if ( !mCalSys ) { mCalSys = KGlobal::locale()->calendar(); } return mCalSys; } void CalPrintPluginBase::setCalendarSystem( const KCalendarSystem *calsys ) { mCalSys = calsys; } int CalPrintPluginBase::headerHeight() const { if ( mHeaderHeight >= 0 ) { return mHeaderHeight; } else if ( orientation() == QPrinter::Portrait ) { return PORTRAIT_HEADER_HEIGHT; } else { return LANDSCAPE_HEADER_HEIGHT; } } void CalPrintPluginBase::setHeaderHeight( const int height ) { mHeaderHeight = height; } int CalPrintPluginBase::subHeaderHeight() const { return mSubHeaderHeight; } void CalPrintPluginBase::setSubHeaderHeight( const int height ) { mSubHeaderHeight = height; } int CalPrintPluginBase::footerHeight() const { if ( !mPrintFooter ) { return 0; } if ( mFooterHeight >= 0 ) { return mFooterHeight; } else if ( orientation() == QPrinter::Portrait ) { return PORTRAIT_FOOTER_HEIGHT; } else { return LANDSCAPE_FOOTER_HEIGHT; } } void CalPrintPluginBase::setFooterHeight( const int height ) { mFooterHeight = height; } int CalPrintPluginBase::margin() const { return mMargin; } void CalPrintPluginBase::setMargin( const int margin ) { mMargin = margin; } int CalPrintPluginBase::padding() const { return mPadding; } void CalPrintPluginBase::setPadding( const int padding ) { mPadding = padding; } int CalPrintPluginBase::borderWidth() const { return mBorder; } void CalPrintPluginBase::setBorderWidth( const int borderwidth ) { mBorder = borderwidth; } void CalPrintPluginBase::drawBox( QPainter &p, int linewidth, const QRect &rect ) { QPen pen( p.pen() ); QPen oldpen( pen ); // no border if ( linewidth >= 0 ) { pen.setWidth( linewidth ); p.setPen( pen ); } else { p.setPen( Qt::NoPen ); } p.drawRect( rect ); p.setPen( oldpen ); } void CalPrintPluginBase::drawShadedBox( QPainter &p, int linewidth, const QBrush &brush, const QRect &rect ) { QBrush oldbrush( p.brush() ); p.setBrush( brush ); drawBox( p, linewidth, rect ); p.setBrush( oldbrush ); } void CalPrintPluginBase::printEventString( QPainter &p, const QRect &box, const QString &str, int flags ) { QRect newbox( box ); newbox.adjust( 3, 1, -1, -1 ); p.drawText( newbox, ( flags == -1 ) ? ( Qt::AlignTop | Qt::AlignLeft | Qt::TextWordWrap ) : flags, str ); } void CalPrintPluginBase::showEventBox( QPainter &p, int linewidth, const QRect &box, const KCalCore::Incidence::Ptr &incidence, const QString &str, int flags ) { QPen oldpen( p.pen() ); QBrush oldbrush( p.brush() ); QColor bgColor( categoryBgColor( incidence ) ); if ( mUseColors & bgColor.isValid() ) { p.setBrush( bgColor ); } else { p.setBrush( QColor( 232, 232, 232 ) ); } drawBox( p, ( linewidth > 0 ) ? linewidth : EVENT_BORDER_WIDTH, box ); if ( mUseColors && bgColor.isValid() ) { p.setPen( getTextColor( bgColor ) ); } printEventString( p, box, str, flags ); p.setPen( oldpen ); p.setBrush( oldbrush ); } void CalPrintPluginBase::drawSubHeaderBox( QPainter &p, const QString &str, const QRect &box ) { drawShadedBox( p, BOX_BORDER_WIDTH, QColor( 232, 232, 232 ), box ); QFont oldfont( p.font() ); p.setFont( QFont( QLatin1String("sans-serif"), 10, QFont::Bold ) ); p.drawText( box, Qt::AlignCenter | Qt::AlignVCenter, str ); p.setFont( oldfont ); } void CalPrintPluginBase::drawVerticalBox( QPainter &p, int linewidth, const QRect &box, const QString &str, int flags ) { p.save(); p.rotate( -90 ); QRect rotatedBox( -box.top() - box.height(), box.left(), box.height(), box.width() ); showEventBox( p, linewidth, rotatedBox, KCalCore::Incidence::Ptr(), str, ( flags == -1 ) ? Qt::AlignLeft | Qt::AlignVCenter | Qt::TextSingleLine : flags ); p.restore(); } /* * Return value: If expand, bottom of the printed box, otherwise vertical end * of the printed contents inside the box. */ int CalPrintPluginBase::drawBoxWithCaption( QPainter &p, const QRect &allbox, const QString &caption, const QString &contents, bool sameLine, bool expand, const QFont &captionFont, const QFont &textFont, bool richContents ) { QFont oldFont( p.font() ); // QFont captionFont( "sans-serif", 11, QFont::Bold ); // QFont textFont( "sans-serif", 11, QFont::Normal ); // QFont captionFont( "Tahoma", 11, QFont::Bold ); // QFont textFont( "Tahoma", 11, QFont::Normal ); QRect box( allbox ); // Bounding rectangle for caption, single-line, clip on the right QRect captionBox( box.left() + padding(), box.top() + padding(), 0, 0 ); p.setFont( captionFont ); captionBox = p.boundingRect( captionBox, Qt::AlignLeft | Qt::AlignTop | Qt::TextSingleLine, caption ); p.setFont( oldFont ); if ( captionBox.right() > box.right() ) { captionBox.setRight( box.right() ); } if ( expand && captionBox.bottom() + padding() > box.bottom() ) { box.setBottom( captionBox.bottom() + padding() ); } // Bounding rectangle for the contents (if any), word break, clip on the bottom QRect textBox( captionBox ); if ( !contents.isEmpty() ) { if ( sameLine ) { textBox.setLeft( captionBox.right() + padding() ); } else { textBox.setTop( captionBox.bottom() + padding() ); } textBox.setRight( box.right() ); } drawBox( p, BOX_BORDER_WIDTH, box ); p.setFont( captionFont ); p.drawText( captionBox, Qt::AlignLeft | Qt::AlignTop | Qt::TextSingleLine, caption ); if ( !contents.isEmpty() ) { if ( sameLine ) { QString contentText = toPlainText( contents ); p.setFont( textFont ); p.drawText( textBox, Qt::AlignLeft | Qt::AlignTop | Qt::TextSingleLine, contents ); } else { QTextDocument rtb; int borderWidth = 2 * BOX_BORDER_WIDTH; if ( richContents ) { rtb.setHtml( contents ); } else { rtb.setPlainText( contents ); } int boxHeight = allbox.height(); if ( !sameLine ) { boxHeight -= captionBox.height(); } rtb.setPageSize( QSize( textBox.width(), boxHeight ) ); rtb.setDefaultFont( textFont ); p.save(); p.translate( textBox.x() - borderWidth, textBox.y() ); QRect clipBox( 0, 0, box.width(), boxHeight ); rtb.drawContents( &p, clipBox ); p.restore(); textBox.setBottom( textBox.y() + rtb.documentLayout()->documentSize().height() ); } } p.setFont( oldFont ); if ( expand ) { return box.bottom(); } else { return textBox.bottom(); } } int CalPrintPluginBase::drawHeader( QPainter &p, const QString &title, const QDate &month1, const QDate &month2, const QRect &allbox, bool expand, QColor backColor ) { // print previous month for month view, print current for to-do, day and week int smallMonthWidth = ( allbox.width() / 4 ) - 10; if ( smallMonthWidth > 100 ) { smallMonthWidth = 100; } QRect box( allbox ); QRect textRect( allbox ); QFont oldFont( p.font() ); QFont newFont( QLatin1String("sans-serif"), ( textRect.height() < 60 ) ? 16 : 18, QFont::Bold ); if ( expand ) { p.setFont( newFont ); QRect boundingR = p.boundingRect( textRect, Qt::AlignLeft | Qt::AlignVCenter | Qt::TextWordWrap, title ); p.setFont( oldFont ); int h = boundingR.height(); if ( h > allbox.height() ) { box.setHeight( h ); textRect.setHeight( h ); } } if ( !backColor.isValid() ) { backColor = QColor( 232, 232, 232 ); } drawShadedBox( p, BOX_BORDER_WIDTH, backColor, box ); #if 0 // current month title left justified, prev month, next month right justified QRect monthbox2( box.right()-10-smallMonthWidth, box.top(), smallMonthWidth, box.height() ); if ( month2.isValid() ) { drawSmallMonth( p, QDate( month2.year(), month2.month(), 1 ), monthbox2 ); textRect.setRight( monthbox2.left() ); } QRect monthbox1( monthbox2.left()-10-smallMonthWidth, box.top(), smallMonthWidth, box.height() ); if ( month1.isValid() ) { drawSmallMonth( p, QDate( month1.year(), month1.month(), 1 ), monthbox1 ); textRect.setRight( monthbox1.left() ); } // Set the margins p.setFont( newFont ); textRect.adjust( 5, 0, 0, 0 ); p.drawText( textRect, Qt::AlignLeft | Qt::AlignVCenter | Qt::TextWordWrap, title ); p.setFont( oldFont ); #endif // prev month left, current month centered, next month right QRect monthbox2( box.right()-10-smallMonthWidth, box.top(), smallMonthWidth, box.height() ); if ( month2.isValid() ) { drawSmallMonth( p, QDate( month2.year(), month2.month(), 1 ), monthbox2 ); textRect.setRight( monthbox2.left() ); } QRect monthbox1( box.left()+10, box.top(), smallMonthWidth, box.height() ); if ( month1.isValid() ) { drawSmallMonth( p, QDate( month1.year(), month1.month(), 1 ), monthbox1 ); textRect.setLeft( monthbox1.right() ); } // Set the margins p.setFont( newFont ); p.drawText( textRect, Qt::AlignCenter | Qt::AlignVCenter | Qt::TextWordWrap, title ); p.setFont( oldFont ); return textRect.bottom(); } int CalPrintPluginBase::drawFooter( QPainter &p, const QRect &footbox ) { QFont oldfont( p.font() ); p.setFont( QFont( QLatin1String("sans-serif"), 6 ) ); QFontMetrics fm( p.font() ); QString dateStr = KGlobal::locale()->formatDateTime( QDateTime::currentDateTime(), KLocale::LongDate ); p.drawText( footbox, Qt::AlignCenter | Qt::AlignVCenter | Qt::TextSingleLine, i18nc( "print date: formatted-datetime", "printed: %1", dateStr ) ); p.setFont( oldfont ); return footbox.bottom(); } void CalPrintPluginBase::drawSmallMonth( QPainter &p, const QDate &qd, const QRect &box ) { int weekdayCol = weekdayColumn( qd.dayOfWeek() ); int month = qd.month(); QDate monthDate( QDate( qd.year(), qd.month(), 1 ) ); // correct begin of week QDate monthDate2( monthDate.addDays( -weekdayCol ) ); double cellWidth = double( box.width() ) / double( 7 ); int rownr = 3 + ( qd.daysInMonth() + weekdayCol - 1 ) / 7; // 3 Pixel after month name, 2 after day names, 1 after the calendar double cellHeight = ( box.height() - 5 ) / rownr; QFont oldFont( p.font() ); p.setFont( QFont( QLatin1String("sans-serif"), int(cellHeight-2), QFont::Normal ) ); // draw the title if ( mCalSys ) { QRect titleBox( box ); titleBox.setHeight( int( cellHeight + 1 ) ); p.drawText( titleBox, Qt::AlignTop | Qt::AlignHCenter, mCalSys->monthName( qd ) ); } // draw days of week QRect wdayBox( box ); wdayBox.setTop( int( box.top() + 3 + cellHeight ) ); wdayBox.setHeight( int( 2 * cellHeight ) - int( cellHeight ) ); if ( mCalSys ) { for ( int col = 0; col < 7; ++col ) { QString tmpStr = mCalSys->weekDayName( monthDate2 )[0].toUpper(); wdayBox.setLeft( int( box.left() + col * cellWidth ) ); wdayBox.setRight( int( box.left() + ( col + 1 ) * cellWidth ) ); p.drawText( wdayBox, Qt::AlignCenter, tmpStr ); monthDate2 = monthDate2.addDays( 1 ); } } // draw separator line int calStartY = wdayBox.bottom() + 2; p.drawLine( box.left(), calStartY, box.right(), calStartY ); monthDate = monthDate.addDays( -weekdayCol ); for ( int row = 0; row < (rownr-2); row++ ) { for ( int col = 0; col < 7; col++ ) { if ( monthDate.month() == month ) { QRect dayRect( int( box.left() + col * cellWidth ), int( calStartY + row * cellHeight ), 0, 0 ); dayRect.setRight( int( box.left() + ( col + 1 ) * cellWidth ) ); dayRect.setBottom( int( calStartY + ( row + 1 ) * cellHeight ) ); p.drawText( dayRect, Qt::AlignCenter, QString::number( monthDate.day() ) ); } monthDate = monthDate.addDays(1); } } p.setFont( oldFont ); } /* * This routine draws a header box over the main part of the calendar * containing the days of the week. */ void CalPrintPluginBase::drawDaysOfWeek( QPainter &p, const QDate &fromDate, const QDate &toDate, const QRect &box ) { double cellWidth = double( box.width() ) / double( fromDate.daysTo( toDate ) + 1 ); QDate cellDate( fromDate ); QRect dateBox( box ); int i = 0; while ( cellDate <= toDate ) { dateBox.setLeft( box.left() + int( i * cellWidth ) ); dateBox.setRight( box.left() + int( ( i + 1 ) * cellWidth ) ); drawDaysOfWeekBox( p, cellDate, dateBox ); cellDate = cellDate.addDays( 1 ); ++i; } } void CalPrintPluginBase::drawDaysOfWeekBox( QPainter &p, const QDate &qd, const QRect &box ) { drawSubHeaderBox( p, ( mCalSys ) ? ( mCalSys->weekDayName( qd ) ) : QString(), box ); } void CalPrintPluginBase::drawTimeLine( QPainter &p, const QTime &fromTime, const QTime &toTime, const QRect &box ) { drawBox( p, BOX_BORDER_WIDTH, box ); int totalsecs = fromTime.secsTo( toTime ); float minlen = (float)box.height() * 60. / (float)totalsecs; float cellHeight = ( 60. * (float)minlen ); float currY = box.top(); // TODO: Don't use half of the width, but less, for the minutes! int xcenter = box.left() + box.width() / 2; QTime curTime( fromTime ); QTime endTime( toTime ); if ( fromTime.minute() > 30 ) { curTime = QTime( fromTime.hour()+1, 0, 0 ); } else if ( fromTime.minute() > 0 ) { curTime = QTime( fromTime.hour(), 30, 0 ); float yy = currY + minlen * (float)fromTime.secsTo( curTime ) / 60.; p.drawLine( xcenter, (int)yy, box.right(), (int)yy ); curTime = QTime( fromTime.hour() + 1, 0, 0 ); } currY += ( float( fromTime.secsTo( curTime ) * minlen ) / 60. ); while ( curTime < endTime ) { p.drawLine( box.left(), (int)currY, box.right(), (int)currY ); int newY = (int)( currY + cellHeight / 2. ); QString numStr; if ( newY < box.bottom() ) { QFont oldFont( p.font() ); // draw the time: if ( !KGlobal::locale()->use12Clock() ) { p.drawLine( xcenter, (int)newY, box.right(), (int)newY ); numStr.setNum( curTime.hour() ); if ( cellHeight > 30 ) { p.setFont( QFont( QLatin1String("sans-serif"), 14, QFont::Bold ) ); } else { p.setFont( QFont( QLatin1String("sans-serif"), 12, QFont::Bold ) ); } p.drawText( box.left() + 4, (int)currY + 2, box.width() / 2 - 2, (int)cellHeight, Qt::AlignTop | Qt::AlignRight, numStr ); p.setFont( QFont( QLatin1String("helvetica"), 10, QFont::Normal ) ); p.drawText( xcenter + 4, (int)currY+2, box.width() / 2 + 2, (int)( cellHeight / 2 ) - 3, Qt::AlignTop | Qt::AlignLeft, QLatin1String("00") ); } else { p.drawLine( box.left(), (int)newY, box.right(), (int)newY ); QTime time( curTime.hour(), 0 ); numStr = KGlobal::locale()->formatTime( time ); if ( box.width() < 60 ) { p.setFont( QFont( QLatin1String("sans-serif"), 7, QFont::Bold ) ); // for weekprint } else { p.setFont( QFont( QLatin1String("sans-serif"), 12, QFont::Bold ) ); // for dayprint } p.drawText( box.left() + 2, (int)currY + 2, box.width() - 4, (int)cellHeight / 2 - 3, Qt::AlignTop|Qt::AlignLeft, numStr ); } currY += cellHeight; p.setFont( oldFont ); } // enough space for half-hour line and time if ( curTime.secsTo( endTime ) > 3600 ) { curTime = curTime.addSecs( 3600 ); } else { curTime = endTime; } } } /** prints the all-day box for the agenda print view. if expandable is set, height is the cell height of a single cell, and the returned height will be the total height used for the all-day events. If !expandable, only one cell will be used, and multiple events are concatenated using ", ". */ int CalPrintPluginBase::drawAllDayBox( QPainter &p, const KCalCore::Event::List &eventList_, const QDate &qd, bool expandable, const QRect &box, bool excludeConfidential, bool excludePrivate ) { KCalCore::Event::List::Iterator it; int offset = box.top(); QString multiDayStr; KCalCore::Event::List eventList = eventList_; KCalCore::Event::Ptr hd = holidayEvent( qd ); if ( hd ) { eventList.prepend( hd ); } it = eventList.begin(); while ( it != eventList.end() ) { KCalCore::Event::Ptr currEvent = *it; if ( ( excludeConfidential && currEvent->secrecy() == KCalCore::Incidence::SecrecyConfidential ) || ( excludePrivate && currEvent->secrecy() == KCalCore::Incidence::SecrecyPrivate ) ) { continue; } if ( currEvent && currEvent->allDay() ) { // set the colors according to the categories if ( expandable ) { QRect eventBox( box ); eventBox.setTop( offset ); showEventBox( p, EVENT_BORDER_WIDTH, eventBox, currEvent, currEvent->summary() ); offset += box.height(); } else { if ( !multiDayStr.isEmpty() ) { multiDayStr += QLatin1String(", "); } multiDayStr += currEvent->summary(); } it = eventList.erase( it ); } else { ++it; } } int ret = box.height(); QRect eventBox( box ); if ( !expandable ) { if ( !multiDayStr.isEmpty() ) { drawShadedBox( p, BOX_BORDER_WIDTH, QColor( 180, 180, 180 ), eventBox ); printEventString( p, eventBox, multiDayStr ); } else { drawBox( p, BOX_BORDER_WIDTH, eventBox ); } } else { ret = offset - box.top(); eventBox.setBottom( ret ); drawBox( p, BOX_BORDER_WIDTH, eventBox ); } return ret; } void CalPrintPluginBase::drawAgendaDayBox( QPainter &p, const KCalCore::Event::List &events, const QDate &qd, bool expandable, const QTime &fromTime, const QTime &toTime, const QRect &oldbox, bool includeDescription, bool excludeTime, bool excludeConfidential, bool excludePrivate, const QList &workDays ) { QTime myFromTime, myToTime; if ( fromTime.isValid() ) { myFromTime = fromTime; } else { myFromTime = QTime( 0, 0, 0 ); } if ( toTime.isValid() ) { myToTime = toTime; } else { myToTime = QTime( 23, 59, 59 ); } if ( !workDays.contains( qd ) ) { drawShadedBox( p, BOX_BORDER_WIDTH, QColor( 232, 232, 232 ), oldbox ); } else { drawBox( p, BOX_BORDER_WIDTH, oldbox ); } QRect box( oldbox ); // Account for the border with and cut away that margin from the interior // box.setRight( box.right()-BOX_BORDER_WIDTH ); if ( expandable ) { // Adapt start/end times to include complete events Q_FOREACH ( const KCalCore::Event::Ptr &event, events ) { Q_ASSERT( event ); if ( ( excludeConfidential && event->secrecy() == KCalCore::Incidence::SecrecyConfidential ) || ( excludePrivate && event->secrecy() == KCalCore::Incidence::SecrecyPrivate ) ) { continue; } // skip items without times so that we do not adjust for all day items if ( event->allDay() ) { continue; } if ( event->dtStart().time() < myFromTime ) { myFromTime = event->dtStart().time(); } if ( event->dtEnd().time() > myToTime ) { myToTime = event->dtEnd().time(); } } } // calculate the height of a cell and of a minute int totalsecs = myFromTime.secsTo( myToTime ); float minlen = box.height() * 60. / totalsecs; float cellHeight = 60. * minlen; float currY = box.top(); // print grid: QTime curTime( QTime( myFromTime.hour(), 0, 0 ) ); currY += myFromTime.secsTo( curTime ) * minlen / 60; while ( curTime < myToTime && curTime.isValid() ) { if ( currY > box.top() ) { p.drawLine( box.left(), int( currY ), box.right(), int( currY ) ); } currY += cellHeight / 2; if ( ( currY > box.top() ) && ( currY < box.bottom() ) ) { // enough space for half-hour line QPen oldPen( p.pen() ); p.setPen( QColor( 192, 192, 192 ) ); p.drawLine( box.left(), int( currY ), box.right(), int( currY ) ); p.setPen( oldPen ); } if ( curTime.secsTo( myToTime ) > 3600 ) { curTime = curTime.addSecs( 3600 ); } else { curTime = myToTime; } currY += cellHeight / 2; } KDateTime startPrintDate = KDateTime( qd, myFromTime ); KDateTime endPrintDate = KDateTime( qd, myToTime ); // Calculate horizontal positions and widths of events taking into account // overlapping events QList cells; Akonadi::Item::List::ConstIterator itEvents; foreach ( const KCalCore::Event::Ptr &event, events ) { if ( event->allDay() ) { continue; } QList times = event->startDateTimesForDate( qd ); for ( QList::ConstIterator it = times.constBegin(); it != times.constEnd(); ++it ) { cells.append( new PrintCellItem( event, (*it), event->endDateForStart( *it ) ) ); } } QListIterator it1( cells ); while ( it1.hasNext() ) { CellItem *placeItem = it1.next(); CellItem::placeItem( cells, placeItem ); } QListIterator it2( cells ); while ( it2.hasNext() ) { PrintCellItem *placeItem = static_cast( it2.next() ); drawAgendaItem( placeItem, p, startPrintDate, endPrintDate, minlen, box, includeDescription, excludeTime ); } } void CalPrintPluginBase::drawAgendaItem( PrintCellItem *item, QPainter &p, const KDateTime &startPrintDate, const KDateTime &endPrintDate, float minlen, const QRect &box, bool includeDescription, bool excludeTime ) { KCalCore::Event::Ptr event = item->event(); // start/end of print area for event KDateTime startTime = item->start(); KDateTime endTime = item->end(); if ( ( startTime < endPrintDate && endTime > startPrintDate ) || ( endTime > startPrintDate && startTime < endPrintDate ) ) { if ( startTime < startPrintDate ) { startTime = startPrintDate; } if ( endTime > endPrintDate ) { endTime = endPrintDate; } int currentWidth = box.width() / item->subCells(); int currentX = box.left() + item->subCell() * currentWidth; int currentYPos = int( box.top() + startPrintDate.secsTo( startTime ) * minlen / 60. ); int currentHeight = int( box.top() + startPrintDate.secsTo( endTime ) * minlen / 60. ) - currentYPos; QRect eventBox( currentX, currentYPos, currentWidth, currentHeight ); QString str; if ( excludeTime ) { if ( event->location().isEmpty() ) { str = cleanStr( event->summary() ); } else { str = i18nc( "summary, location", "%1, %2", cleanStr( event->summary() ), cleanStr( event->location() ) ); } } else { if ( event->location().isEmpty() ) { str = i18nc( "starttime - endtime summary", "%1-%2 %3", KGlobal::locale()->formatTime( item->start().toLocalZone().time() ), KGlobal::locale()->formatTime( item->end().toLocalZone().time() ), cleanStr( event->summary() ) ); } else { str = i18nc( "starttime - endtime summary, location", "%1-%2 %3, %4", KGlobal::locale()->formatTime( item->start().toLocalZone().time() ), KGlobal::locale()->formatTime( item->end().toLocalZone().time() ), cleanStr( event->summary() ), cleanStr( event->location() ) ); } } if ( includeDescription && !event->description().isEmpty() ) { str += QLatin1Char('\n'); if ( event->descriptionIsRich() ) { str += toPlainText( event->description() ); } else { str += event->description(); } } QFont oldFont( p.font() ); if ( eventBox.height() < 24 ) { if ( eventBox.height() < 12 ) { if ( eventBox.height() < 8 ) { p.setFont( QFont( QLatin1String("sans-serif"), 4 ) ); } else { p.setFont( QFont( QLatin1String("sans-serif"), 5 ) ); } } else { p.setFont( QFont( QLatin1String("sans-serif"), 6 ) ); } } else { p.setFont( QFont( QLatin1String("sans-serif"), 8 ) ); } showEventBox( p, EVENT_BORDER_WIDTH, eventBox, event, str ); p.setFont( oldFont ); } } void CalPrintPluginBase::drawDayBox( QPainter &p, const QDate &qd, const QTime &fromTime, const QTime &toTime, const QRect &box, bool fullDate, bool printRecurDaily, bool printRecurWeekly, bool singleLineLimit, bool showNoteLines, bool includeDescription, bool excludeConfidential, bool excludePrivate ) { QString dayNumStr; const KLocale *local = KGlobal::locale(); QTime myFromTime, myToTime; if ( fromTime.isValid() ) { myFromTime = fromTime; } else { myFromTime = QTime( 0, 0, 0 ); } if ( toTime.isValid() ) { myToTime = toTime; } else { myToTime = QTime( 23, 59, 59 ); } if ( fullDate && mCalSys ) { dayNumStr = i18nc( "weekday, shortmonthname daynumber", "%1, %2 %3", mCalSys->weekDayName( qd ), mCalSys->monthName( qd, KCalendarSystem::ShortName ), qd.day() ); } else { dayNumStr = QString::number( qd.day() ); } QRect subHeaderBox( box ); subHeaderBox.setHeight( mSubHeaderHeight ); drawShadedBox( p, BOX_BORDER_WIDTH, p.background(), box ); drawShadedBox( p, 0, QColor( 232, 232, 232 ), subHeaderBox ); drawBox( p, BOX_BORDER_WIDTH, box ); QString hstring( holidayString( qd ) ); const QFont oldFont( p.font() ); QRect headerTextBox( subHeaderBox ); headerTextBox.setLeft( subHeaderBox.left() + 5 ); headerTextBox.setRight( subHeaderBox.right() - 5 ); if ( !hstring.isEmpty() ) { p.setFont( QFont( QLatin1String("sans-serif"), 8, QFont::Bold, true ) ); p.drawText( headerTextBox, Qt::AlignLeft | Qt::AlignVCenter, hstring ); } p.setFont( QFont( QLatin1String("sans-serif"), 10, QFont::Bold ) ); p.drawText( headerTextBox, Qt::AlignRight | Qt::AlignVCenter, dayNumStr ); const KCalCore::Event::List eventList = mCalendar->events( qd, KSystemTimeZones::local(), KCalCore::EventSortStartDate, KCalCore::SortDirectionAscending ); QString timeText; p.setFont( QFont( QLatin1String("sans-serif"), 7 ) ); int textY = mSubHeaderHeight; // gives the relative y-coord of the next printed entry unsigned int visibleEventsCounter = 0; Q_FOREACH ( const KCalCore::Event::Ptr &currEvent, eventList ) { Q_ASSERT( currEvent ); if ( !currEvent->allDay() ) { if ( currEvent->dtEnd().toLocalZone().time() <= myFromTime || currEvent->dtStart().toLocalZone().time() > myToTime ) { continue; } } if ( ( !printRecurDaily && currEvent->recurrenceType() == KCalCore::Recurrence::rDaily ) || ( !printRecurWeekly && currEvent->recurrenceType() == KCalCore::Recurrence::rWeekly ) ) { continue; } if ( ( excludeConfidential && currEvent->secrecy() == KCalCore::Incidence::SecrecyConfidential ) || ( excludePrivate && currEvent->secrecy() == KCalCore::Incidence::SecrecyPrivate ) ) { continue; } if ( currEvent->allDay() || currEvent->isMultiDay() ) { timeText.clear(); } else { timeText = local->formatTime( currEvent->dtStart().toLocalZone().time() ) + QLatin1Char(' '); } p.save(); setColorsByIncidenceCategory( p, currEvent ); QString summaryStr = currEvent->summary(); if ( !currEvent->location().isEmpty() ) { summaryStr = i18nc( "summary, location", "%1, %2", summaryStr, currEvent->location() ); } drawIncidence( p, box, timeText, summaryStr, currEvent->description(), textY, singleLineLimit, includeDescription, currEvent->descriptionIsRich() ); p.restore(); visibleEventsCounter++; if ( textY >= box.height() ) { const QChar downArrow( 0x21e3 ); const unsigned int invisibleIncidences = ( eventList.count() - visibleEventsCounter ) + mCalendar->todos( qd ).count(); if ( invisibleIncidences > 0 ) { const QString warningMsg = QString::fromLatin1( "%1 (%2)" ).arg( downArrow ).arg( invisibleIncidences ); QFontMetrics fm( p.font() ); QRect msgRect = fm.boundingRect( warningMsg ); msgRect.setRect( box.right() - msgRect.width() - 2, box.bottom() - msgRect.height() - 2, msgRect.width(), msgRect.height() ); p.save(); p.setPen( Qt::red ); //krazy:exclude=qenums we don't allow custom print colors p.drawText( msgRect, Qt::AlignLeft, warningMsg ); p.restore(); } break; } } if ( textY < box.height() ) { KCalCore::Todo::List todos = mCalendar->todos( qd ); foreach ( const KCalCore::Todo::Ptr &todo, todos ) { if ( !todo->allDay() ) { if ( ( todo->hasDueDate() && todo->dtDue().toLocalZone().time() <= myFromTime ) || ( todo->hasStartDate() && todo->dtStart().toLocalZone().time() > myToTime ) ) { continue; } } if ( ( !printRecurDaily && todo->recurrenceType() == KCalCore::Recurrence::rDaily ) || ( !printRecurWeekly && todo->recurrenceType() == KCalCore::Recurrence::rWeekly ) ) { continue; } if ( ( excludeConfidential && todo->secrecy() == KCalCore::Incidence::SecrecyConfidential ) || ( excludePrivate && todo->secrecy() == KCalCore::Incidence::SecrecyPrivate ) ) { continue; } if ( todo->hasStartDate() && !todo->allDay() ) { timeText = KGlobal::locale()->formatTime( todo->dtStart().toLocalZone().time() ) + QLatin1Char(' '); } else { timeText.clear(); } p.save(); setColorsByIncidenceCategory( p, todo ); QString summaryStr = todo->summary(); if ( !todo->location().isEmpty() ) { summaryStr = i18nc( "summary, location", "%1, %2", summaryStr, todo->location() ); } QString str; if ( todo->hasDueDate() ) { if ( !todo->allDay() ) { str = i18nc( "to-do summary (Due: datetime)", "%1 (Due: %2)", summaryStr, KGlobal::locale()->formatDateTime( todo->dtDue().toLocalZone() ) ); } else { str = i18nc( "to-do summary (Due: date)", "%1 (Due: %2)", summaryStr, KGlobal::locale()->formatDate( todo->dtDue().toLocalZone().date(), KLocale::ShortDate ) ); } } else { str = summaryStr; } drawIncidence( p, box, timeText, i18n( "To-do: %1", str ), todo->description(), textY, singleLineLimit, includeDescription, todo->descriptionIsRich() ); p.restore(); } } if ( showNoteLines ) { drawNoteLines( p, box, box.y() + textY ); } p.setFont( oldFont ); } void CalPrintPluginBase::drawIncidence( QPainter &p, const QRect &dayBox, const QString &time, const QString &summary, const QString &description, int &textY, bool singleLineLimit, bool includeDescription, bool richDescription ) { kDebug() << "summary =" << summary << ", singleLineLimit=" << singleLineLimit; int flags = Qt::AlignLeft | Qt::OpaqueMode; QFontMetrics fm = p.fontMetrics(); const int borderWidth = p.pen().width() + 1; QRect timeBound = p.boundingRect( dayBox.x() + borderWidth, dayBox.y() + textY, dayBox.width(), fm.lineSpacing(), flags, time ); int summaryWidth = time.isEmpty() ? 0 : timeBound.width() + 3; QRect summaryBound = QRect( dayBox.x() + borderWidth + summaryWidth, dayBox.y() + textY + 1, dayBox.width() - summaryWidth - ( borderWidth * 2 ), dayBox.height() - textY ); QString summaryText = summary; QString descText = toPlainText( description ); bool boxOverflow = false; if ( singleLineLimit ) { if ( includeDescription && !descText.isEmpty() ) { summaryText += QLatin1String(", ") + descText; } int totalHeight = fm.lineSpacing() + borderWidth; int textBoxHeight = ( totalHeight > ( dayBox.height() - textY ) ) ? dayBox.height() - textY : totalHeight; summaryBound.setHeight( textBoxHeight ); QRect lineRect( dayBox.x() + borderWidth, dayBox.y() + textY, dayBox.width() - ( borderWidth * 2 ), textBoxHeight ); drawBox( p, 1, lineRect ); if ( !time.isEmpty() ) { p.drawText( timeBound, flags, time ); } p.drawText( summaryBound, flags, summaryText ); } else { QTextDocument textDoc; QTextCursor textCursor( &textDoc ); if ( richDescription ) { QTextCursor textCursor( &textDoc ); textCursor.insertText( summaryText ); if ( includeDescription && !description.isEmpty() ) { textCursor.insertText( QLatin1String("\n") ); textCursor.insertHtml( description ); } } else { textCursor.insertText( summaryText ); if ( includeDescription && !descText.isEmpty() ) { textCursor.insertText( QLatin1String("\n") ); textCursor.insertText( descText ); } } textDoc.setPageSize( QSize( summaryBound.width(), summaryBound.height() ) ); p.save(); QRect clipBox( 0, 0, summaryBound.width(), summaryBound.height() ); p.setFont( p.font() ); p.translate( summaryBound.x(), summaryBound.y() ); summaryBound.setHeight( textDoc.documentLayout()->documentSize().height() ); if ( summaryBound.bottom() > dayBox.bottom() ) { summaryBound.setBottom( dayBox.bottom() ); } clipBox.setHeight( summaryBound.height() ); p.restore(); p.save(); QRect backBox( timeBound.x(), timeBound.y(), dayBox.width() - ( borderWidth * 2 ), clipBox.height() ); drawBox( p, 1, backBox ); if ( !time.isEmpty() ) { if ( timeBound.bottom() > dayBox.bottom() ) { timeBound.setBottom( dayBox.bottom() ); } timeBound.moveTop ( timeBound.y() + ( summaryBound.height() - timeBound.height() ) / 2 ); p.drawText( timeBound, flags, time ); } p.translate( summaryBound.x(), summaryBound.y() ); textDoc.drawContents( &p, clipBox ); p.restore(); boxOverflow = textDoc.pageCount() > 1; } if ( summaryBound.bottom() < dayBox.bottom() ) { QPen oldPen( p.pen() ); p.setPen( QPen() ); p.drawLine( dayBox.x(), summaryBound.bottom(), dayBox.x() + dayBox.width(), summaryBound.bottom() ); p.setPen( oldPen ); } textY += summaryBound.height(); // show that we have overflowed the box if ( boxOverflow ) { QPolygon poly(3); int x = dayBox.x() + dayBox.width(); int y = dayBox.y() + dayBox.height(); poly.setPoint( 0, x - 10, y ); poly.setPoint( 1, x, y - 10 ); poly.setPoint( 2, x, y ); QBrush oldBrush( p.brush() ); p.setBrush( QBrush( Qt::black ) ); p.drawPolygon(poly); p.setBrush( oldBrush ); textY = dayBox.height(); } } void CalPrintPluginBase::drawWeek( QPainter &p, const QDate &qd, const QTime &fromTime, const QTime &toTime, const QRect &box, bool singleLineLimit, bool showNoteLines, bool includeDescription, bool excludeConfidential, bool excludePrivate ) { QDate weekDate = qd; const bool portrait = ( box.height() > box.width() ); int cellWidth; int vcells; if ( portrait ) { cellWidth = box.width() / 2; vcells=3; } else { cellWidth = box.width() / 6; vcells=1; } const int cellHeight = box.height() / vcells; // correct begin of week int weekdayCol = weekdayColumn( qd.dayOfWeek() ); weekDate = qd.addDays( -weekdayCol ); for ( int i = 0; i < 7; ++i, weekDate = weekDate.addDays(1) ) { // Saturday and sunday share a cell, so we have to special-case sunday int hpos = ( ( i < 6 ) ? i : ( i - 1 ) ) / vcells; int vpos = ( ( i < 6 ) ? i : ( i - 1 ) ) % vcells; QRect dayBox( box.left() + cellWidth * hpos, box.top() + cellHeight * vpos + ( ( i == 6 ) ? ( cellHeight / 2 ) : 0 ), cellWidth, ( i < 5 ) ? ( cellHeight ) : ( cellHeight / 2 ) ); drawDayBox( p, weekDate, fromTime, toTime, dayBox, true, true, true, singleLineLimit, showNoteLines, includeDescription, excludeConfidential, excludePrivate ); } // for i through all weekdays } void CalPrintPluginBase::drawDays( QPainter &p, const QDate &start, const QDate &end, const QTime &fromTime, const QTime &toTime, const QRect &box, bool singleLineLimit, bool showNoteLines, bool includeDescription, bool excludeConfidential, bool excludePrivate ) { const int numberOfDays = start.daysTo( end ) + 1; int vcells; const bool portrait = ( box.height() > box.width() ); int cellWidth; if ( portrait ) { // 2 columns vcells = qCeil( static_cast( numberOfDays ) / 2.0 ); if ( numberOfDays > 1 ) { cellWidth = box.width() / 2; } else { cellWidth = box.width(); } } else { // landscape: N columns vcells = 1; cellWidth = box.width() / numberOfDays; } const int cellHeight = box.height() / vcells; QDate weekDate = start; for ( int i = 0; i < numberOfDays; ++i, weekDate = weekDate.addDays(1) ) { const int hpos = i / vcells; const int vpos = i % vcells; const QRect dayBox( box.left() + cellWidth * hpos, box.top() + cellHeight * vpos, cellWidth, cellHeight ); drawDayBox( p, weekDate, fromTime, toTime, dayBox, true, true, true, singleLineLimit, showNoteLines, includeDescription, excludeConfidential, excludePrivate ); } // for i through all selected days } void CalPrintPluginBase::drawTimeTable( QPainter &p, const QDate &fromDate, const QDate &toDate, bool expandable, const QTime &fromTime, const QTime &toTime, const QRect &box, bool includeDescription, bool excludeTime, bool excludeConfidential, bool excludePrivate ) { QTime myFromTime = fromTime; QTime myToTime = toTime; if ( expandable ) { QDate curDate( fromDate ); KDateTime::Spec timeSpec = KSystemTimeZones::local(); while ( curDate <= toDate ) { KCalCore::Event::List eventList = mCalendar->events( curDate, timeSpec ); Q_FOREACH ( const KCalCore::Event::Ptr &event, eventList ) { Q_ASSERT( event ); if ( event->allDay() ) { continue; } if ( event->dtStart().time() < myFromTime ) { myFromTime = event->dtStart().time(); } if ( event->dtEnd().time() > myToTime ) { myToTime = event->dtEnd().time(); } } curDate = curDate.addDays(1); } } // timeline is 1 hour: int alldayHeight = (int)( 3600. * box.height() / ( myFromTime.secsTo( myToTime ) + 3600. ) ); int timelineWidth = TIMELINE_WIDTH; QRect dowBox( box ); dowBox.setLeft( box.left() + timelineWidth ); dowBox.setHeight( mSubHeaderHeight ); drawDaysOfWeek( p, fromDate, toDate, dowBox ); QRect tlBox( box ); tlBox.setWidth( timelineWidth ); tlBox.setTop( dowBox.bottom() + BOX_BORDER_WIDTH + alldayHeight ); drawTimeLine( p, myFromTime, myToTime, tlBox ); // draw each day QDate curDate( fromDate ); KDateTime::Spec timeSpec = KSystemTimeZones::local(); int i=0; double cellWidth = double( dowBox.width() ) / double( fromDate.daysTo( toDate ) + 1 ); const QList workDays = CalendarSupport::workDays( fromDate, toDate ); while ( curDate <= toDate ) { QRect allDayBox( dowBox.left()+int( i * cellWidth ), dowBox.bottom() + BOX_BORDER_WIDTH, int( ( i + 1 ) * cellWidth ) - int( i * cellWidth ), alldayHeight ); QRect dayBox( allDayBox ); dayBox.setTop( tlBox.top() ); dayBox.setBottom( box.bottom() ); KCalCore::Event::List eventList = mCalendar->events( curDate, timeSpec, KCalCore::EventSortStartDate, KCalCore::SortDirectionAscending ); alldayHeight = drawAllDayBox( p, eventList, curDate, false, allDayBox, excludeConfidential, excludePrivate ); drawAgendaDayBox( p, eventList, curDate, false, myFromTime, myToTime, dayBox, includeDescription, excludeTime, excludeConfidential, excludePrivate, workDays ); ++i; curDate = curDate.addDays(1); } } class MonthEventStruct { public: MonthEventStruct() : event(0) {} MonthEventStruct( const KDateTime &s, const KDateTime &e, const KCalCore::Event::Ptr &ev ) { event = ev; start = s; end = e; if ( event->allDay() ) { start = KDateTime( start.date(), QTime( 0, 0, 0 ) ); end = KDateTime( end.date().addDays(1), QTime( 0, 0, 0 ) ).addSecs(-1); } } bool operator < ( const MonthEventStruct &mes ) { return start < mes.start; } KDateTime start; KDateTime end; KCalCore::Event::Ptr event; }; void CalPrintPluginBase::drawMonth( QPainter &p, const QDate &dt, const QRect &box, int maxdays, int subDailyFlags, int holidaysFlags, bool excludeConfidential, bool excludePrivate ) { const KCalendarSystem *calsys = calendarSystem(); QRect subheaderBox( box ); subheaderBox.setHeight( subHeaderHeight() ); QRect borderBox( box ); borderBox.setTop( subheaderBox.bottom() + 1 ); drawSubHeaderBox( p, calsys->monthName( dt ), subheaderBox ); // correct for half the border width int correction = ( BOX_BORDER_WIDTH/*-1*/ ) / 2; QRect daysBox( borderBox ); daysBox.adjust( correction, correction, -correction, -correction ); int daysinmonth = calsys->daysInMonth( dt ); if ( maxdays <= 0 ) { maxdays = daysinmonth; } int d; float dayheight = float( daysBox.height() ) / float( maxdays ); QColor holidayColor( 240, 240, 240 ); QColor workdayColor( 255, 255, 255 ); int dayNrWidth = p.fontMetrics().width( QLatin1String("99") ); // Fill the remaining space (if a month has less days than others) with a crossed-out pattern if ( daysinmonth workDays; { QDate startDate; QDate endDate; calsys->setDate( startDate, dt.year(), dt.month(), 1 ); calsys->setDate( endDate, dt.year(), dt.month(), daysinmonth ); workDays = CalendarSupport::workDays( startDate, endDate ); } for ( d = 0; d < daysinmonth; ++d ) { QDate day; calsys->setDate( day, dt.year(), dt.month(), d+1 ); QRect dayBox( daysBox.left()/*+rand()%50*/, daysBox.top() + qRound( dayheight * d ), daysBox.width()/*-rand()%50*/, 0 ); // FIXME: When using a border width of 0 for event boxes, // don't let the rectangles overlap, i.e. subtract 1 from the top or bottom! dayBox.setBottom( daysBox.top() + qRound( dayheight * ( d + 1 ) ) - 1 ); p.setBrush( workDays.contains( day ) ? workdayColor : holidayColor ); p.drawRect( dayBox ); QRect dateBox( dayBox ); dateBox.setWidth( dayNrWidth + 3 ); p.drawText( dateBox, Qt::AlignRight | Qt::AlignVCenter | Qt::TextSingleLine, QString::number( d + 1 ) ); } p.setBrush( oldbrush ); int xstartcont = box.left() + dayNrWidth + 5; QDate start, end; calsys->setDate( start, dt.year(), dt.month(), 1 ); end = calsys->addMonths( start, 1 ); end = calsys->addDays( end, -1 ); const KCalCore::Event::List events = mCalendar->events( start, end ); QMap textEvents; QList timeboxItems; // 1) For multi-day events, show boxes spanning several cells, use CellItem // print the summary vertically // 2) For sub-day events, print the concated summaries into the remaining // space of the box (optional, depending on the given flags) // 3) Draw some kind of timeline showing free and busy times // Holidays QList holidays; for ( QDate d( start ); d <= end; d = d.addDays(1) ) { KCalCore::Event::Ptr e = holidayEvent( d ); if ( e ) { holidays.append( e ); if ( holidaysFlags & TimeBoxes ) { timeboxItems.append( new PrintCellItem( e, KDateTime( d, QTime( 0, 0, 0 ) ), KDateTime( d.addDays(1), QTime( 0, 0, 0 ) ) ) ); } if ( holidaysFlags & Text ) { textEvents[d.day()] << e->summary(); } } } QList monthentries; KDateTime::Spec timeSpec = KSystemTimeZones::local(); Q_FOREACH ( const KCalCore::Event::Ptr &e, events ) { if ( !e ) { continue; } if ( ( excludeConfidential && e->secrecy() == KCalCore::Incidence::SecrecyConfidential ) || ( excludePrivate && e->secrecy() == KCalCore::Incidence::SecrecyPrivate ) ) { continue; } if ( e->recurs() ) { if ( e->recursOn( start, timeSpec ) ) { // This occurrence has possibly started before the beginning of the // month, so obtain the start date before the beginning of the month QList starttimes = e->startDateTimesForDate( start ); QList::ConstIterator it = starttimes.constBegin(); for ( ; it != starttimes.constEnd(); ++it ) { monthentries.append( MonthEventStruct( *it, e->endDateForStart( *it ), e ) ); } } // Loop through all remaining days of the month and check if the event // begins on that day (don't use Event::recursOn, as that will // also return events that have started earlier. These start dates // however, have already been treated! KCalCore::Recurrence *recur = e->recurrence(); QDate d1( start.addDays(1) ); while ( d1 <= end ) { if ( recur->recursOn( d1, timeSpec ) ) { KCalCore::TimeList times( recur->recurTimesOn( d1, timeSpec ) ); for ( KCalCore::TimeList::ConstIterator it = times.constBegin(); it != times.constEnd(); ++it ) { KDateTime d1start( d1, *it, timeSpec ); monthentries.append( MonthEventStruct( d1start, e->endDateForStart( d1start ), e ) ); } } d1 = d1.addDays(1); } } else { monthentries.append( MonthEventStruct( e->dtStart(), e->dtEnd(), e ) ); } } // TODO: to port the month entries sorting // qSort( monthentries.begin(), monthentries.end() ); QList::ConstIterator mit = monthentries.constBegin(); KDateTime endofmonth( end, QTime( 0, 0, 0 ) ); endofmonth = endofmonth.addDays(1); for ( ; mit != monthentries.constEnd(); ++mit ) { if ( (*mit).start.date() == (*mit).end.date() ) { // Show also single-day events as time line boxes if ( subDailyFlags & TimeBoxes ) { timeboxItems.append( new PrintCellItem( (*mit).event, (*mit).start, (*mit).end ) ); } // Show as text in the box if ( subDailyFlags & Text ) { textEvents[(*mit).start.date().day()] << (*mit).event->summary(); } } else { // Multi-day events are always shown as time line boxes KDateTime thisstart( (*mit).start ); KDateTime thisend( (*mit).end ); if ( thisstart.date() < start ) { thisstart.setDate( start ); } if ( thisend > endofmonth ) { thisend = endofmonth; } timeboxItems.append( new PrintCellItem( (*mit).event, thisstart, thisend ) ); } } // For Multi-day events, line them up nicely so that the boxes don't overlap QListIterator it1( timeboxItems ); while ( it1.hasNext() ) { CellItem *placeItem = it1.next(); CellItem::placeItem( timeboxItems, placeItem ); } KDateTime starttime( start, QTime( 0, 0, 0 ) ); int newxstartcont = xstartcont; QFont oldfont( p.font() ); p.setFont( QFont( QLatin1String("sans-serif"), 7 ) ); while ( it1.hasNext() ) { PrintCellItem *placeItem = static_cast( it1.next() ); int minsToStart = starttime.secsTo( placeItem->start() ) / 60; int minsToEnd = starttime.secsTo( placeItem->end() ) / 60; QRect eventBox( xstartcont + placeItem->subCell() * 17, daysBox.top() + qRound( double( minsToStart * daysBox.height() ) / double( maxdays * 24 * 60 ) ), 14, 0 ); eventBox.setBottom( daysBox.top() + qRound( double( minsToEnd * daysBox.height() ) / double( maxdays * 24 * 60 ) ) ); drawVerticalBox( p, 0, eventBox, placeItem->event()->summary() ); newxstartcont = qMax( newxstartcont, eventBox.right() ); } xstartcont = newxstartcont; // For Single-day events, simply print their summaries into the remaining // space of the day's cell for ( int d=0; d monthLast ) ) { p.setBackground( back.darker( 120 ) ); darkbg = true; } QRect dayBox( coledges[col], rowedges[row], coledges[col + 1] - coledges[col], rowedges[row + 1] - rowedges[row] ); drawDayBox( p, monthDate, fromTime, toTime, dayBox, false, recurDaily, recurWeekly, singleLineLimit, showNoteLines, includeDescription, excludeConfidential, excludePrivate ); if ( darkbg ) { p.setBackground( back ); darkbg = false; } monthDate = monthDate.addDays(1); } } } void CalPrintPluginBase::drawTodoLines( QPainter &p, const QString &entry, int x, int &y, int width, int pageHeight, bool richTextEntry, QList &startPoints, bool connectSubTodos ) { QString plainEntry = ( richTextEntry ) ? toPlainText( entry ) : entry; QRect textrect( 0, 0, width, -1 ); int flags = Qt::AlignLeft; QFontMetrics fm = p.fontMetrics(); QStringList lines = plainEntry.split( QLatin1Char( '\n' ) ); for ( int currentLine = 0; currentLine < lines.count(); currentLine++ ) { // split paragraphs into lines KWordWrap *ww = KWordWrap::formatText( fm, textrect, flags, lines[currentLine] ); QStringList textLine = ww->wrappedString().split( QLatin1Char( '\n' ) ); delete ww; // print each individual line for ( int lineCount = 0; lineCount < textLine.count(); lineCount++ ) { if ( y >= pageHeight ) { if ( connectSubTodos ) { for ( int i = 0; i < startPoints.size(); ++i ) { TodoParentStart *rct; rct = startPoints.at( i ); int start = rct->mRect.bottom() + 1; int center = rct->mRect.left() + ( rct->mRect.width() / 2 ); int to = y; if ( !rct->mSamePage ) { start = 0; } if ( rct->mHasLine ) { p.drawLine( center, start, center, to ); } rct->mSamePage = false; } } y = 0; mPrinter->newPage(); } y += fm.height(); p.drawText( x, y, textLine[lineCount] ); } } } void CalPrintPluginBase::drawTodo( int &count, const KCalCore::Todo::Ptr &todo, QPainter &p, KCalCore::TodoSortField sortField, KCalCore::SortDirection sortDir, bool connectSubTodos, bool strikeoutCompleted, bool desc, int posPriority, int posSummary, int posDueDt, int posPercentComplete, int level, int x, int &y, int width, int pageHeight, const KCalCore::Todo::List &todoList, TodoParentStart *r, bool excludeConfidential, bool excludePrivate ) { QString outStr; const KLocale *local = KGlobal::locale(); QRect rect; TodoParentStart startpt; // This list keeps all starting points of the parent to-dos so the connection // lines of the tree can easily be drawn (needed if a new page is started) static QList startPoints; if ( level < 1 ) { startPoints.clear(); } y += 10; // Compute the right hand side of the to-do box int rhs = posPercentComplete; if ( rhs < 0 ) { rhs = posDueDt; //not printing percent completed } if ( rhs < 0 ) { rhs = x + width; //not printing due dates either } int left = posSummary + ( level * 10 ); // If this is a sub-to-do, r will not be 0, and we want the LH side // of the priority line up to the RH side of the parent to-do's priority bool showPriority = posPriority >= 0; int lhs = posPriority; if ( r ) { lhs = r->mRect.right() + 1; } outStr.setNum( todo->priority() ); rect = p.boundingRect( lhs, y + 10, 5, -1, Qt::AlignCenter, outStr ); // Make it a more reasonable size rect.setWidth( 18 ); rect.setHeight( 18 ); // Draw a checkbox p.setBrush( QBrush( Qt::NoBrush ) ); p.drawRect( rect ); if ( todo->isCompleted() ) { // cross out the rectangle for completed to-dos p.drawLine( rect.topLeft(), rect.bottomRight() ); p.drawLine( rect.topRight(), rect.bottomLeft() ); } lhs = rect.right() + 3; // Priority if ( todo->priority() > 0 && showPriority ) { p.drawText( rect, Qt::AlignCenter, outStr ); } startpt.mRect = rect; //save for later // Connect the dots if ( r && level > 0 && connectSubTodos ) { int bottom; int center( r->mRect.left() + ( r->mRect.width() / 2 ) ); int to( rect.top() + ( rect.height() / 2 ) ); int endx( rect.left() ); p.drawLine( center, to, endx, to ); // side connector if ( r->mSamePage ) { bottom = r->mRect.bottom() + 1; } else { bottom = 0; } p.drawLine( center, bottom, center, to ); } // summary outStr = todo->summary(); rect = p.boundingRect( lhs, rect.top(), ( rhs - ( left + rect.width() + 5 ) ), -1, Qt::TextWordWrap, outStr ); QRect newrect; QFont newFont( p.font() ); QFont oldFont( p.font() ); if ( todo->isCompleted() && strikeoutCompleted ) { newFont.setStrikeOut( true ); p.setFont( newFont ); } p.drawText( rect, Qt::TextWordWrap, outStr, &newrect ); p.setFont( oldFont ); // due date if ( todo->hasDueDate() && posDueDt >= 0 ) { outStr = local->formatDate( todo->dtDue().toLocalZone().date(), KLocale::ShortDate ); rect = p.boundingRect( posDueDt, y, x + width, -1, Qt::AlignTop | Qt::AlignLeft, outStr ); p.drawText( rect, Qt::AlignTop | Qt::AlignLeft, outStr ); } // percentage completed bool showPercentComplete = posPercentComplete >= 0; if ( showPercentComplete ) { int lwidth = 24; int lheight = 12; //first, draw the progress bar int progress = (int)( ( lwidth * todo->percentComplete() ) / 100.0 + 0.5 ); p.setBrush( QBrush( Qt::NoBrush ) ); p.drawRect( posPercentComplete, y+3, lwidth, lheight ); if ( progress > 0 ) { p.setBrush( QColor( 128, 128, 128 ) ); p.drawRect( posPercentComplete, y+3, progress, lheight ); } //now, write the percentage outStr = i18n( "%1%", todo->percentComplete() ); rect = p.boundingRect( posPercentComplete+lwidth+3, y, x + width, -1, Qt::AlignTop | Qt::AlignLeft, outStr ); p.drawText( rect, Qt::AlignTop | Qt::AlignLeft, outStr ); } y += 10; // Make a list of all the sub-to-dos related to this to-do. KCalCore::Todo::List t; - KCalCore::Incidence::List relations = mCalendar->childIncidences( todo->uid() ); + KCalCore::Incidence::List relations = mCalendar->childIncidences( todo ); foreach ( const KCalCore::Incidence::Ptr &incidence, relations ) { // In the future, to-dos might also be related to events // Manually check if the sub-to-do is in the list of to-dos to print // The problem is that relations() does not apply filters, so // we need to compare manually with the complete filtered list! KCalCore::Todo::Ptr subtodo = incidence.dynamicCast(); if ( !subtodo ) continue; #ifdef AKONADI_PORT_DISABLED if ( subtodo && todoList.contains( subtodo ) ) { #else bool subtodoOk = false; if ( subtodo ) { foreach ( const KCalCore::Todo::Ptr &tt, todoList ) { if ( tt == subtodo ) { subtodoOk = true; break; } } } if ( subtodoOk ) { #endif if ( ( excludeConfidential && subtodo->secrecy() == KCalCore::Incidence::SecrecyConfidential ) || ( excludePrivate && subtodo->secrecy() == KCalCore::Incidence::SecrecyPrivate ) ) { continue; } t.append( subtodo ); } } // has sub-todos? startpt.mHasLine = ( relations.size() > 0 ); startPoints.append( &startpt ); // description if ( !todo->description().isEmpty() && desc ) { y = newrect.bottom() + 5; drawTodoLines( p, todo->description(), left, y, width - ( left + 10 - x ), pageHeight, todo->descriptionIsRich(), startPoints, connectSubTodos ); } else { y += 10; } // Sort the sub-to-dos and print them #ifdef AKONADI_PORT_DISABLED KCalCore::Todo::List sl = mCalendar->sortTodos( &t, sortField, sortDir ); #else KCalCore::Todo::List tl; foreach ( const KCalCore::Todo::Ptr &todo, t ) { tl.append( todo ); } KCalCore::Todo::List sl = mCalendar->sortTodos( tl, sortField, sortDir ); #endif int subcount = 0; foreach ( const KCalCore::Todo::Ptr &isl, sl ) { count++; if ( ++subcount == sl.size() ) { startpt.mHasLine = false; } drawTodo( count, isl, p, sortField, sortDir, connectSubTodos, strikeoutCompleted, desc, posPriority, posSummary, posDueDt, posPercentComplete, level+1, x, y, width, pageHeight, todoList, &startpt, excludeConfidential, excludePrivate ); } startPoints.removeAll( &startpt ); } int CalPrintPluginBase::weekdayColumn( int weekday ) { int w = weekday + 7 - KGlobal::locale()->weekStartDay(); return w % 7; } void CalPrintPluginBase::drawTextLines( QPainter &p, const QString &entry, int x, int &y, int width, int pageHeight, bool richTextEntry ) { QString plainEntry = ( richTextEntry ) ? toPlainText( entry ) : entry; QRect textrect( 0, 0, width, -1 ); int flags = Qt::AlignLeft; QFontMetrics fm = p.fontMetrics(); QStringList lines = plainEntry.split( QLatin1Char( '\n' ) ); for ( int currentLine = 0; currentLine < lines.count(); currentLine++ ) { // split paragraphs into lines KWordWrap *ww = KWordWrap::formatText( fm, textrect, flags, lines[currentLine] ); QStringList textLine = ww->wrappedString().split( QLatin1Char( '\n' ) ); delete ww; // print each individual line for ( int lineCount = 0; lineCount < textLine.count(); lineCount++ ) { if ( y >= pageHeight ) { y = 0; mPrinter->newPage(); } y += fm.height(); p.drawText( x, y, textLine[lineCount] ); } } } void CalPrintPluginBase::drawJournal( const KCalCore::Journal::Ptr &journal, QPainter &p, int x, int &y, int width, int pageHeight ) { QFont oldFont( p.font() ); p.setFont( QFont( QLatin1String("sans-serif"), 15 ) ); QString headerText; QString dateText( KGlobal::locale()->formatDate( journal->dtStart().toLocalZone().date(), KLocale::LongDate ) ); if ( journal->summary().isEmpty() ) { headerText = dateText; } else { headerText = i18nc( "Description - date", "%1 - %2", journal->summary(), dateText ); } QRect rect( p.boundingRect( x, y, width, -1, Qt::TextWordWrap, headerText ) ); if ( rect.bottom() > pageHeight ) { // Start new page... y = 0; mPrinter->newPage(); rect = p.boundingRect( x, y, width, -1, Qt::TextWordWrap, headerText ); } QRect newrect; p.drawText( rect, Qt::TextWordWrap, headerText, &newrect ); p.setFont( oldFont ); y = newrect.bottom() + 4; p.drawLine( x + 3, y, x + width - 6, y ); y += 5; if ( !( journal->organizer()->fullName().isEmpty() ) ) { drawTextLines( p, i18n( "Person: %1", journal->organizer()->fullName() ), x, y, width, pageHeight, false ); y += 7; } if ( !( journal->description().isEmpty() ) ) { drawTextLines( p, journal->description(), x, y, width, pageHeight, journal->descriptionIsRich() ); y += 7; } y += 10; } void CalPrintPluginBase::drawSplitHeaderRight( QPainter &p, const QDate &fd, const QDate &td, const QDate &, int width, int height ) { QFont oldFont( p.font() ); QPen oldPen( p.pen() ); QPen pen( Qt::black, 4 ); QString title; if ( mCalSys ) { if ( fd.month() == td.month() ) { title = i18nc( "Date range: Month dayStart - dayEnd", "%1 %2 - %3", mCalSys->monthName( fd.month(), KCalendarSystem::LongName ), mCalSys->formatDate( fd, KLocale::Day, KLocale::LongNumber ), mCalSys->formatDate( td, KLocale::Day, KLocale::LongNumber ) ); } else { title = i18nc( "Date range: monthStart dayStart - monthEnd dayEnd", "%1 %2 - %3 %4", mCalSys->monthName( fd.month(), KCalendarSystem::LongName ), mCalSys->formatDate( fd, KLocale::Day, KLocale::LongNumber ), mCalSys->monthName( td.month(), KCalendarSystem::LongName ), mCalSys->formatDate( td, KLocale::Day, KLocale::LongNumber ) ); } } if ( height < 60 ) { p.setFont( QFont( QLatin1String("Times"), 22 ) ); } else { p.setFont( QFont( QLatin1String("Times"), 28 ) ); } int lineSpacing = p.fontMetrics().lineSpacing(); p.drawText( 0, 0, width, lineSpacing, Qt::AlignRight | Qt::AlignTop, title ); title.truncate(0); p.setPen( pen ); p.drawLine( 300, lineSpacing, width, lineSpacing ); p.setPen( oldPen ); if ( height < 60 ) { p.setFont( QFont( QLatin1String("Times"), 14, QFont::Bold, true ) ); } else { p.setFont( QFont( QLatin1String("Times"), 18, QFont::Bold, true ) ); } title += QString::number( fd.year() ); p.drawText( 0, lineSpacing, width, lineSpacing, Qt::AlignRight | Qt::AlignTop, title ); p.setFont( oldFont ); } void CalPrintPluginBase::drawNoteLines( QPainter &p, const QRect &box, int startY ) { int lineHeight = int( p.fontMetrics().lineSpacing() * 1.5 ); int linePos = box.y(); int startPos = startY; // adjust line to start at multiple from top of box for alignment while ( linePos < startPos ) { linePos += lineHeight; } QPen oldPen( p.pen() ); p.setPen( Qt::DotLine ); while ( linePos < box.bottom() ) { p.drawLine( box.left() + padding(), linePos, box.right() - padding(), linePos ); linePos += lineHeight; } p.setPen( oldPen ); } QString CalPrintPluginBase::toPlainText( const QString &htmlText ) { // this converts possible rich text to plain text return QTextDocumentFragment::fromHtml( htmlText ).toPlainText(); } diff --git a/calendarviews/CMakeLists.txt b/calendarviews/CMakeLists.txt index 9dff9a882a..bb897fc23e 100644 --- a/calendarviews/CMakeLists.txt +++ b/calendarviews/CMakeLists.txt @@ -1,122 +1,122 @@ # The following macros can be defined to alter behavior. # (if desired, use add_definitions() to define them) # # EVENTVIEWS_NODECOS - turns-off decoration plugins in views. # No idea why you would want to define this, perhaps to save # screen real estate? But there are a config options for that. project(calendarView) add_definitions( -DQT_NO_CAST_FROM_ASCII ) add_definitions( -DQT_NO_CAST_TO_ASCII ) if(KDEPIM_BUILD_EXAMPLES) add_subdirectory(viewerapp) endif() option(EVENTVIEWS_NODECOS "Turn-off decoration plugins in views." FALSE) if(EVENTVIEWS_NODECOS) add_definitions(-DEVENTVIEWS_NODECOS) endif() include_directories( ${Boost_INCLUDE_DIR} ${CMAKE_SOURCE_DIR}/akonadi_next ${CMAKE_SOURCE_DIR}/calendarsupport ${CMAKE_BINARY_DIR}/calendarsupport ${CMAKE_SOURCE_DIR}/calendarviews ${CMAKE_SOURCE_DIR}/libkdepim/ ${ZLIB_INCLUDE_DIRS} ) if(KDEPIM_MOBILE_UI) add_definitions(-DKORG_NODND) endif() ########### next target ############### set(eventviews_LIB_SRCS eventview_p.cpp eventview.cpp helper.cpp prefs.cpp + viewcalendar.cpp # Agenda view specific code. agenda/agenda.cpp agenda/agendaitem.cpp agenda/agendaview.cpp agenda/alternatelabel.cpp agenda/calendardecoration.cpp agenda/decorationlabel.cpp agenda/timelabels.cpp agenda/timelabelszone.cpp agenda/timescaleconfigdialog.cpp - agenda/viewcalendar.cpp journal/journalframe.cpp journal/journalview.cpp list/listview.cpp month/monthgraphicsitems.cpp month/monthitem.cpp month/monthscene.cpp month/monthview.cpp multiagenda/multiagendaview.cpp todo/tododelegates.cpp todo/todomodel.cpp todo/incidencetreemodel.cpp todo/todoviewquickaddline.cpp todo/todoviewquicksearch.cpp todo/todoviewsortfilterproxymodel.cpp todo/todoviewview.cpp todo/todoview.cpp timeline/timelineview.cpp timeline/timelineitem.cpp timeline/timelineview_p.cpp timespent/timespentview.cpp whatsnext/whatsnextview.cpp ) kde4_add_kcfg_files(eventviews_LIB_SRCS prefs_base.kcfgc) kde4_add_ui_files(eventviews_LIB_SRCS agenda/timescaleedit_base.ui ) kde4_add_library(eventviews ${LIBRARY_TYPE} ${eventviews_LIB_SRCS}) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${KDE4_ENABLE_EXCEPTIONS}") target_link_libraries(eventviews ${QT_QTCORE_LIBRARY} ${QT_QTGUI_LIBRARY} ${QT_QTUITOOLS_LIBRARY} ${KDE4_KDEUI_LIBS} ${KDEPIMLIBS_KMIME_LIBS} ${KDE4_KCMUTILS_LIBS} ${ZLIB_LIBRARIES} ${KDEPIMLIBS_KPIMIDENTITIES_LIBS} ${KDE4_KIO_LIBS} ${KDEPIMLIBS_AKONADI_LIBS} ${KDEPIMLIBS_KHOLIDAYS_LIBS} kdepim kdgantt2 calendarsupport akonadi_next ${KDEPIMLIBS_KCALCORE_LIBS} ${KDEPIMLIBS_KCALUTILS_LIBS} akonadi-calendar ) set_target_properties(eventviews PROPERTIES VERSION ${GENERIC_LIB_VERSION} SOVERSION ${GENERIC_LIB_SOVERSION}) install(TARGETS eventviews ${INSTALL_TARGETS_DEFAULT_ARGS}) install(FILES agenda/calendardecoration.desktop DESTINATION ${SERVICETYPES_INSTALL_DIR}) diff --git a/calendarviews/agenda/agenda.cpp b/calendarviews/agenda/agenda.cpp index 5130eea1e3..c5babafedc 100644 --- a/calendarviews/agenda/agenda.cpp +++ b/calendarviews/agenda/agenda.cpp @@ -1,2339 +1,2316 @@ /* Copyright (c) 2001 Cornelius Schumacher Copyright (C) 2003-2004 Reinhold Kainhofer Copyright (C) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net Author: Kevin Krammer, krake@kdab.com Author: Sergio Martins, sergio@kdab.com Marcus Bains line. Copyright (c) 2001 Ali Rahimi This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #include "agenda.h" #include "agendaitem.h" #include "agendaview.h" #include "viewcalendar.h" #include "prefs.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // for fabs() using namespace EventViews; /////////////////////////////////////////////////////////////////////////////// class MarcusBains::Private { public: Private( EventView *eventView, Agenda *agenda ) : mEventView( eventView ), mAgenda( agenda ), mTimer( 0 ), mTimeBox( 0 ), mOldTodayCol( -1 ) { } int todayColumn() const; public: EventView *mEventView; Agenda *mAgenda; QTimer *mTimer; QLabel *mTimeBox; // Label showing the current time KDateTime mOldDateTime; int mOldTodayCol; }; int MarcusBains::Private::todayColumn() const { const QDate currentDate = QDate::currentDate(); int col = 0; const KCalCore::DateList dateList = mAgenda->dateList(); foreach ( const QDate &date, dateList ) { if ( date == currentDate ) { return QApplication::isRightToLeft() ? mAgenda->columns() - 1 - col : col; } ++col; } return -1; } MarcusBains::MarcusBains( EventView *eventView, Agenda *agenda ) : QFrame( agenda ), d( new Private( eventView, agenda ) ) { d->mTimeBox = new QLabel( d->mAgenda ); d->mTimeBox->setAlignment( Qt::AlignRight | Qt::AlignBottom ); d->mTimer = new QTimer( this ); d->mTimer->setSingleShot( true ); connect( d->mTimer, SIGNAL(timeout()), this, SLOT(updateLocation()) ); d->mTimer->start( 0 ); } MarcusBains::~MarcusBains() { delete d; } void MarcusBains::updateLocation() { updateLocationRecalc(); } void MarcusBains::updateLocationRecalc( bool recalculate ) { const bool showSeconds = d->mEventView->preferences()->marcusBainsShowSeconds(); const QColor color = d->mEventView->preferences()->agendaMarcusBainsLineLineColor(); const KDateTime now = KDateTime::currentLocalDateTime(); const QTime time = now.time(); if (now.date() != d->mOldDateTime.date()) { recalculate = true; // New day } const int todayCol = recalculate ? d->todayColumn() : d->mOldTodayCol; // Number of minutes since beginning of the day const int minutes = time.hour() * 60 + time.minute(); const int minutesPerCell = 24 * 60 / d->mAgenda->rows(); d->mOldDateTime = now; d->mOldTodayCol = todayCol; int y = int( minutes * d->mAgenda->gridSpacingY() / minutesPerCell ); int x = int( d->mAgenda->gridSpacingX() * todayCol ); bool hideIt = !( d->mEventView->preferences()->marcusBainsEnabled() ); if ( !isHidden() && ( hideIt || ( todayCol < 0 ) ) ) { hide(); d->mTimeBox->hide(); return; } if ( isHidden() && !hideIt ) { show(); d->mTimeBox->show(); } /* Line */ // It seems logical to adjust the line width with the label's font weight const int fw = d->mEventView->preferences()->agendaMarcusBainsLineFont().weight(); setLineWidth( 1 + abs( fw - QFont::Normal ) / QFont::Light ); setFrameStyle( QFrame::HLine | QFrame::Plain ); QPalette pal = palette(); pal.setColor( QPalette::Window, color ); // for Oxygen pal.setColor( QPalette::WindowText, color ); // for Plastique setPalette( pal ); if ( recalculate ) { setFixedSize( int( d->mAgenda->gridSpacingX() ), 1 ); } move( x, y ); raise(); /* Label */ d->mTimeBox->setFont( d->mEventView->preferences()->agendaMarcusBainsLineFont() ); QPalette pal1 = d->mTimeBox->palette(); pal1.setColor( QPalette::WindowText, color ); d->mTimeBox->setPalette( pal1 ); d->mTimeBox->setText( KGlobal::locale()->formatTime( time, showSeconds ) ); d->mTimeBox->adjustSize(); if ( y - d->mTimeBox->height() >= 0 ) { y -= d->mTimeBox->height(); } else { y++; } if ( x - d->mTimeBox->width() + d->mAgenda->gridSpacingX() > 0 ) { x += int( d->mAgenda->gridSpacingX() - d->mTimeBox->width() - 1 ); } else { x++; } d->mTimeBox->move( x, y ); d->mTimeBox->raise(); if ( showSeconds || recalculate ) { d->mTimer->start( 1000 ); } else { d->mTimer->start( 1000 * ( 60 - time.second() ) ); } } //////////////////////////////////////////////////////////////////////////// class Agenda::Private { public: Private( AgendaView *agendaView, QScrollArea *scrollArea, int columns, int rows, int rowSize, bool isInteractive ) : mAgendaView( agendaView ), mScrollArea( scrollArea ), mAllDayMode( false ), mColumns( columns ), mRows( rows ), mGridSpacingX( 0.0 ), mGridSpacingY( rowSize ), mDesiredGridSpacingY( rowSize ), mChanger( 0 ), mResizeBorderWidth( 0 ), mScrollBorderWidth( 0 ), mScrollDelay( 0 ), mScrollOffset( 0 ), mWorkingHoursEnable( false ), mHolidayMask( 0 ), mWorkingHoursYTop( 0 ), mWorkingHoursYBottom( 0 ), mHasSelection( 0 ), mSelectedId( -1 ), mMarcusBains( 0 ), mActionType( Agenda::NOP ), mItemMoved( false ), mOldLowerScrollValue( 0 ), mOldUpperScrollValue( 0 ), mReturnPressed( false ), mIsInteractive( isInteractive ) { if ( mGridSpacingY < 4 || mGridSpacingY > 30 ) { mGridSpacingY = 10; } } public: PrefsPtr preferences() const { return mAgendaView->preferences(); } - bool isQueuedForDeletion( const QString &uid ) const + bool isQueuedForDeletion( const KCalCore::Incidence::Ptr &inc ) const { // if mAgendaItemsById contains it it means that a createAgendaItem() was called // before the previous agenda items were deleted. - return mItemsQueuedForDeletion.contains( uid ) && !mAgendaItemsById.contains( uid ); + return mItemsQueuedForDeletion.contains( inc ) && !mAgendaItemsById.contains( inc ); } - QMultiHash mAgendaItemsById; // It's a QMultiHash because recurring incidences might have many agenda items - QSet mItemsQueuedForDeletion; + QMultiHash mAgendaItemsById; //Multi hash due to recurring events + QSet mItemsQueuedForDeletion; AgendaView *mAgendaView; QScrollArea *mScrollArea; bool mAllDayMode; // Number of Columns/Rows of agenda grid int mColumns; int mRows; // Width and height of agenda cells. mDesiredGridSpacingY is the height // set in the config. The actual height might be larger since otherwise // more than 24 hours might be displayed. double mGridSpacingX; double mGridSpacingY; double mDesiredGridSpacingY; Akonadi::IncidenceChanger *mChanger; // size of border, where mouse action will resize the AgendaItem int mResizeBorderWidth; // size of border, where mouse mve will cause a scroll of the agenda int mScrollBorderWidth; int mScrollDelay; int mScrollOffset; QTimer mScrollUpTimer; QTimer mScrollDownTimer; // Cells to store Move and Resize coordiantes while performing the action QPoint mStartCell; QPoint mEndCell; // Working Hour coordiantes bool mWorkingHoursEnable; QVector *mHolidayMask; int mWorkingHoursYTop; int mWorkingHoursYBottom; // Selection bool mHasSelection; QPoint mSelectionStartPoint; QPoint mSelectionStartCell; QPoint mSelectionEndCell; // List of dates to be displayed KCalCore::DateList mSelectedDates; // The AgendaItem, which has been right-clicked last QPointer mClickedItem; // The AgendaItem, which is being moved/resized QPointer mActionItem; // Currently selected item QPointer mSelectedItem; // Uid of the last selected incidence. Used for reselecting in situations // where the selected item points to a no longer valid incidence, for // example during resource reload. QString mSelectedId; // The Marcus Bains Line widget. MarcusBains *mMarcusBains; MouseActionType mActionType; bool mItemMoved; // List of all Items contained in agenda QList mItems; QList mItemsToDelete; int mOldLowerScrollValue; int mOldUpperScrollValue; bool mReturnPressed; bool mIsInteractive; MultiViewCalendar::Ptr mCalendar; }; /* Create an agenda widget with rows rows and columns columns. */ Agenda::Agenda( AgendaView *agendaView, QScrollArea *scrollArea, int columns, int rows, int rowSize, bool isInteractive ) : QWidget( scrollArea ), d( new Private( agendaView, scrollArea, columns, rows, rowSize, isInteractive ) ) { setMouseTracking( true ); init(); } /* Create an agenda widget with columns columns and one row. This is used for all-day events. */ Agenda::Agenda( AgendaView *agendaView, QScrollArea *scrollArea, int columns, bool isInteractive ) : QWidget( scrollArea ), d( new Private( agendaView, scrollArea, columns, 1, 24, isInteractive ) ) { d->mAllDayMode = true; init(); } Agenda::~Agenda() { delete d->mMarcusBains; delete d; } KCalCore::Incidence::Ptr Agenda::selectedIncidence() const { return d->mSelectedItem ? d->mSelectedItem->incidence() : KCalCore::Incidence::Ptr(); } QDate Agenda::selectedIncidenceDate() const { return d->mSelectedItem ? d->mSelectedItem->occurrenceDate() : QDate(); } QString Agenda::lastSelectedItemId() const { return d->mSelectedId; } void Agenda::init() { setAttribute( Qt::WA_OpaquePaintEvent ); d->mGridSpacingX = static_cast( d->mScrollArea->width() ) / d->mColumns; d->mDesiredGridSpacingY = d->preferences()->hourSize(); if ( d->mDesiredGridSpacingY < 4 || d->mDesiredGridSpacingY > 30 ) { d->mDesiredGridSpacingY = 10; } // make sure that there are not more than 24 per day d->mGridSpacingY = static_cast( height() ) / d->mRows; if ( d->mGridSpacingY < d->mDesiredGridSpacingY ) { d->mGridSpacingY = d->mDesiredGridSpacingY; } d->mResizeBorderWidth = 12; d->mScrollBorderWidth = 12; d->mScrollDelay = 30; d->mScrollOffset = 10; // Grab key strokes for keyboard navigation of agenda. Seems to have no // effect. Has to be fixed. setFocusPolicy( Qt::WheelFocus ); connect( &d->mScrollUpTimer, SIGNAL(timeout()), SLOT(scrollUp()) ); connect( &d->mScrollDownTimer, SIGNAL(timeout()), SLOT(scrollDown()) ); d->mStartCell = QPoint( 0, 0 ); d->mEndCell = QPoint( 0, 0 ); d->mHasSelection = false; d->mSelectionStartPoint = QPoint( 0, 0 ); d->mSelectionStartCell = QPoint( 0, 0 ); d->mSelectionEndCell = QPoint( 0, 0 ); d->mOldLowerScrollValue = -1; d->mOldUpperScrollValue = -1; d->mClickedItem = 0; d->mActionItem = 0; d->mActionType = NOP; d->mItemMoved = false; d->mSelectedItem = 0; d->mSelectedId = -1; setAcceptDrops( true ); installEventFilter( this ); /* resizeContents( int( mGridSpacingX * mColumns ), int( mGridSpacingY * mRows ) ); */ d->mScrollArea->viewport()->update(); // mScrollArea->viewport()->setAttribute( Qt::WA_NoSystemBackground, true ); d->mScrollArea->viewport()->setFocusPolicy( Qt::WheelFocus ); calculateWorkingHours(); connect( verticalScrollBar(), SIGNAL(valueChanged(int)), SLOT(checkScrollBoundaries(int)) ); // Create the Marcus Bains line. if( d->mAllDayMode ) { d->mMarcusBains = 0; } else { d->mMarcusBains = new MarcusBains( d->mAgendaView, this ); } } void Agenda::clear() { qDeleteAll( d->mItems ); qDeleteAll( d->mItemsToDelete ); d->mItems.clear(); d->mItemsToDelete.clear(); d->mAgendaItemsById.clear(); d->mItemsQueuedForDeletion.clear(); d->mSelectedItem = 0; clearSelection(); } void Agenda::clearSelection() { d->mHasSelection = false; d->mActionType = NOP; update(); } void Agenda::marcus_bains() { if ( d->mMarcusBains ) { d->mMarcusBains->updateLocationRecalc( true ); } } void Agenda::changeColumns( int columns ) { if ( columns == 0 ) { kDebug() << "called with argument 0"; return; } clear(); d->mColumns = columns; // setMinimumSize( mColumns * 10, mGridSpacingY + 1 ); // init(); // update(); QResizeEvent event( size(), size() ); QApplication::sendEvent( this, &event ); } int Agenda::columns() const { return d->mColumns; } int Agenda::rows() const { return d->mRows; } double Agenda::gridSpacingX() const { return d->mGridSpacingX; } double Agenda::gridSpacingY() const { return d->mGridSpacingY; } /* This is the eventFilter function, which gets all events from the AgendaItems contained in the agenda. It has to handle moving and resizing for all items. */ bool Agenda::eventFilter ( QObject *object, QEvent *event ) { switch( event->type() ) { case QEvent::MouseButtonPress: case QEvent::MouseButtonDblClick: case QEvent::MouseButtonRelease: case QEvent::MouseMove: return eventFilter_mouse( object, static_cast( event ) ); #ifndef QT_NO_WHEELEVENT case QEvent::Wheel: return eventFilter_wheel( object, static_cast( event ) ); #endif case QEvent::KeyPress: case QEvent::KeyRelease: return eventFilter_key( object, static_cast( event ) ); case ( QEvent::Leave ): #ifndef QT_NO_CURSOR if ( !d->mActionItem ) { setCursor( Qt::ArrowCursor ); } #endif if ( object == this ) { // so timelabels hide the mouse cursor emit leaveAgenda(); } return true; case QEvent::Enter: emit enterAgenda(); return QWidget::eventFilter( object, event ); #ifndef QT_NO_DRAGANDDROP #ifndef KORG_NODND case QEvent::DragEnter: case QEvent::DragMove: case QEvent::DragLeave: case QEvent::Drop: // case QEvent::DragResponse: return eventFilter_drag( object, static_cast( event ) ); #endif #endif default: return QWidget::eventFilter( object, event ); } } bool Agenda::eventFilter_drag( QObject *obj, QDropEvent *de ) { #ifndef QT_NO_DRAGANDDROP const QMimeData *md = de->mimeData(); switch ( de->type() ) { case QEvent::DragEnter: case QEvent::DragMove: if ( !CalendarSupport::canDecode( md ) ) { return false; } if ( CalendarSupport::mimeDataHasIncidence( md ) ) { de->accept(); } else { de->ignore(); } return true; break; case QEvent::DragLeave: return false; break; case QEvent::Drop: { if ( !CalendarSupport::canDecode( md ) ) { return false; } const QList incidenceUrls = CalendarSupport::incidenceItemUrls( md ); const KCalCore::Incidence::List incidences = - CalendarSupport::incidences( md, d->mCalendar->mETMCalendar->timeSpec() ); + CalendarSupport::incidences( md, d->mCalendar->etmCalendar()->timeSpec() ); Q_ASSERT( !incidenceUrls.isEmpty() || !incidences.isEmpty() ); de->setDropAction( Qt::MoveAction ); QWidget *dropTarget = qobject_cast( obj ); QPoint dropPosition = de->pos(); if ( dropTarget && dropTarget != this ) { dropPosition = dropTarget->mapTo( this, dropPosition ); } const QPoint gridPosition = contentsToGrid( dropPosition ); if ( !incidenceUrls.isEmpty() ) { emit droppedIncidences( incidenceUrls, gridPosition, d->mAllDayMode ); } else { emit droppedIncidences( incidences, gridPosition, d->mAllDayMode ); } return true; } break; case QEvent::DragResponse: default: break; } #endif return false; } #ifndef QT_NO_WHEELEVENT bool Agenda::eventFilter_wheel ( QObject *object, QWheelEvent *e ) { QPoint viewportPos; bool accepted=false; if ( ( e->modifiers() & Qt::ShiftModifier ) == Qt::ShiftModifier ) { if ( object != this ) { viewportPos = ( (QWidget *) object )->mapToParent( e->pos() ); } else { viewportPos = e->pos(); } //kDebug() << type:" << e->type() << "delta:" << e->delta(); emit zoomView( -e->delta(), contentsToGrid( viewportPos ), Qt::Horizontal ); accepted = true; } if ( ( e->modifiers() & Qt::ControlModifier ) == Qt::ControlModifier ){ if ( object != this ) { viewportPos = ( (QWidget *)object )->mapToParent( e->pos() ); } else { viewportPos = e->pos(); } emit zoomView( -e->delta(), contentsToGrid( viewportPos ), Qt::Vertical ); emit mousePosSignal( gridToContents( contentsToGrid( viewportPos ) ) ); accepted = true; } if ( accepted ) { e->accept(); } return accepted; } #endif bool Agenda::eventFilter_key( QObject *, QKeyEvent *ke ) { return d->mAgendaView->processKeyEvent( ke ); } bool Agenda::eventFilter_mouse( QObject *object, QMouseEvent *me ) { QPoint viewportPos; if ( object != this ) { viewportPos = static_cast( object )->mapToParent( me->pos() ); } else { viewportPos = me->pos(); } switch ( me->type() ) { case QEvent::MouseButtonPress: if ( object != this ) { if ( me->button() == Qt::RightButton ) { d->mClickedItem = dynamic_cast( object ); if ( d->mClickedItem ) { selectItem( d->mClickedItem ); emit showIncidencePopupSignal( d->mClickedItem->incidence(), d->mClickedItem->occurrenceDate() ); } } else { AgendaItem::QPtr item = dynamic_cast(object); if (item) { KCalCore::Incidence::Ptr incidence = item->incidence(); if ( incidence->isReadOnly() ) { d->mActionItem = 0; } else { d->mActionItem = item; startItemAction( viewportPos ); } // Warning: do selectItem() as late as possible, since all // sorts of things happen during this call. Some can lead to // this filter being run again and mActionItem being set to // null. selectItem( item ); } } } else { if ( me->button() == Qt::RightButton ) { // if mouse pointer is not in selection, select the cell below the cursor QPoint gpos = contentsToGrid( viewportPos ); if ( !ptInSelection( gpos ) ) { d->mSelectionStartCell = gpos; d->mSelectionEndCell = gpos; d->mHasSelection = true; emit newStartSelectSignal(); emit newTimeSpanSignal( d->mSelectionStartCell, d->mSelectionEndCell ); // updateContents(); } showNewEventPopupSignal(); } else { selectItem( 0 ); d->mActionItem = 0; #ifndef QT_NO_CURSOR setCursor( Qt::ArrowCursor ); #endif startSelectAction( viewportPos ); update(); } } break; case QEvent::MouseButtonRelease: if ( d->mActionItem ) { endItemAction(); } else if ( d->mActionType == SELECT ) { endSelectAction( viewportPos ); } // This nasty gridToContents(contentsToGrid(..)) is needed to // avoid an offset of a few pixels. Don't ask me why... emit mousePosSignal( gridToContents( contentsToGrid( viewportPos ) ) ); break; case QEvent::MouseMove: { if ( !d->mIsInteractive ) { return true; } // This nasty gridToContents(contentsToGrid(..)) is needed todos // avoid an offset of a few pixels. Don't ask me why... QPoint indicatorPos = gridToContents( contentsToGrid( viewportPos ) ); if ( object != this ) { AgendaItem::QPtr moveItem = dynamic_cast( object ); KCalCore::Incidence::Ptr incidence = moveItem ? moveItem->incidence() : KCalCore::Incidence::Ptr(); if ( incidence && !incidence->isReadOnly() ) { if ( !d->mActionItem ) { setNoActionCursor( moveItem, viewportPos ); } else { performItemAction( viewportPos ); if ( d->mActionType == MOVE ) { // show cursor at the current begin of the item AgendaItem::QPtr firstItem = d->mActionItem->firstMultiItem(); if ( !firstItem ) { firstItem = d->mActionItem; } indicatorPos = gridToContents( QPoint( firstItem->cellXLeft(), firstItem->cellYTop() ) ); } else if ( d->mActionType == RESIZEBOTTOM ) { // RESIZETOP is handled correctly, only resizebottom works differently indicatorPos = gridToContents( QPoint( d->mActionItem->cellXLeft(), d->mActionItem->cellYBottom() + 1 ) ); } } // If we have an action item } // If move item && !read only } else { if ( d->mActionType == SELECT ) { performSelectAction( viewportPos ); // show cursor at end of timespan if ( ( ( d->mStartCell.y() < d->mEndCell.y() ) && ( d->mEndCell.x() >= d->mStartCell.x() ) ) || ( d->mEndCell.x() > d->mStartCell.x() ) ) { indicatorPos = gridToContents( QPoint( d->mEndCell.x(), d->mEndCell.y() + 1 ) ); } else { indicatorPos = gridToContents( d->mEndCell ); } } } emit mousePosSignal( indicatorPos ); break; } case QEvent::MouseButtonDblClick: if ( object == this ) { selectItem( 0 ); emit newEventSignal(); } else { AgendaItem::QPtr doubleClickedItem = dynamic_cast( object ); if ( doubleClickedItem ) { selectItem( doubleClickedItem ); emit editIncidenceSignal( doubleClickedItem->incidence(), doubleClickedItem->occurrenceDateTime() ); } } break; default: break; } return true; } bool Agenda::ptInSelection( const QPoint &gpos ) const { if ( !d->mHasSelection ) { return false; } else if ( gpos.x() < d->mSelectionStartCell.x() || gpos.x() > d->mSelectionEndCell.x() ) { return false; } else if ( ( gpos.x() == d->mSelectionStartCell.x() ) && ( gpos.y() < d->mSelectionStartCell.y() ) ) { return false; } else if ( ( gpos.x() == d->mSelectionEndCell.x() ) && ( gpos.y() > d->mSelectionEndCell.y() ) ) { return false; } return true; } void Agenda::startSelectAction( const QPoint &viewportPos ) { emit newStartSelectSignal(); d->mActionType = SELECT; d->mSelectionStartPoint = viewportPos; d->mHasSelection = true; QPoint pos = viewportPos ; QPoint gpos = contentsToGrid( pos ); // Store new selection d->mStartCell = gpos; d->mEndCell = gpos; d->mSelectionStartCell = gpos; d->mSelectionEndCell = gpos; // updateContents(); } void Agenda::performSelectAction( const QPoint &pos ) { const QPoint gpos = contentsToGrid( pos ); // Scroll if cursor was moved to upper or lower end of agenda. if ( pos.y() - contentsY() < d->mScrollBorderWidth && contentsY() > 0 ) { d->mScrollUpTimer.start( d->mScrollDelay ); } else if ( contentsY() + d->mScrollArea->viewport()->height() - d->mScrollBorderWidth < pos.y() ) { d->mScrollDownTimer.start( d->mScrollDelay ); } else { d->mScrollUpTimer.stop(); d->mScrollDownTimer.stop(); } if ( gpos != d->mEndCell ) { d->mEndCell = gpos; if ( d->mStartCell.x() > d->mEndCell.x() || ( d->mStartCell.x() == d->mEndCell.x() && d->mStartCell.y() > d->mEndCell.y() ) ) { // backward selection d->mSelectionStartCell = d->mEndCell; d->mSelectionEndCell = d->mStartCell; } else { d->mSelectionStartCell = d->mStartCell; d->mSelectionEndCell = d->mEndCell; } update(); } } void Agenda::endSelectAction( const QPoint ¤tPos ) { d->mScrollUpTimer.stop(); d->mScrollDownTimer.stop(); d->mActionType = NOP; emit newTimeSpanSignal( d->mSelectionStartCell, d->mSelectionEndCell ); if ( d->preferences()->selectionStartsEditor() ) { if ( ( d->mSelectionStartPoint - currentPos ).manhattanLength() > QApplication::startDragDistance() ) { emit newEventSignal(); } } } Agenda::MouseActionType Agenda::isInResizeArea( bool horizontal, const QPoint &pos, AgendaItem::QPtr item ) { if ( !item ) { return NOP; } QPoint gridpos = contentsToGrid( pos ); QPoint contpos = gridToContents( gridpos + QPoint( ( QApplication::isRightToLeft() ) ? 1 : 0, 0 ) ); if ( horizontal ) { int clXLeft = item->cellXLeft(); int clXRight = item->cellXRight(); if ( QApplication::isRightToLeft() ) { int tmp = clXLeft; clXLeft = clXRight; clXRight = tmp; } int gridDistanceX = int( pos.x() - contpos.x() ); if ( gridDistanceX < d->mResizeBorderWidth && clXLeft == gridpos.x() ) { if ( QApplication::isRightToLeft() ) { return RESIZERIGHT; } else { return RESIZELEFT; } } else if ( ( d->mGridSpacingX - gridDistanceX ) < d->mResizeBorderWidth && clXRight == gridpos.x() ) { if ( QApplication::isRightToLeft() ) { return RESIZELEFT; } else { return RESIZERIGHT; } } else { return MOVE; } } else { int gridDistanceY = int( pos.y() - contpos.y() ); if ( gridDistanceY < d->mResizeBorderWidth && item->cellYTop() == gridpos.y() && !item->firstMultiItem() ) { return RESIZETOP; } else if ( ( d->mGridSpacingY - gridDistanceY ) < d->mResizeBorderWidth && item->cellYBottom() == gridpos.y() && !item->lastMultiItem() ) { return RESIZEBOTTOM; } else { return MOVE; } } } void Agenda::startItemAction( const QPoint &pos ) { Q_ASSERT( d->mActionItem ); d->mStartCell = contentsToGrid( pos ); d->mEndCell = d->mStartCell; bool noResize = CalendarSupport::hasTodo( d->mActionItem->incidence() ); d->mActionType = MOVE; if ( !noResize ) { d->mActionType = isInResizeArea( d->mAllDayMode, pos, d->mActionItem ); } d->mActionItem->startMove(); setActionCursor( d->mActionType, true ); } void Agenda::performItemAction( const QPoint &pos ) { QPoint gpos = contentsToGrid( pos ); // Cursor left active agenda area. // This starts a drag. if ( pos.y() < 0 || pos.y() >= contentsY() + d->mScrollArea->viewport()->height() || pos.x() < 0 || pos.x() >= width() ) { if ( d->mActionType == MOVE ) { d->mScrollUpTimer.stop(); d->mScrollDownTimer.stop(); d->mActionItem->resetMove(); placeSubCells( d->mActionItem ); emit startDragSignal( d->mActionItem->incidence() ); #ifndef QT_NO_CURSOR setCursor( Qt::ArrowCursor ); #endif if ( d->mChanger ) { // d->mChanger->cancelChange( d->mActionItem->incidence() ); } d->mActionItem = 0; d->mActionType = NOP; d->mItemMoved = false; return; } } else { setActionCursor( d->mActionType, true ); } // Scroll if item was moved to upper or lower end of agenda. const int distanceToTop = pos.y() - contentsY(); if ( distanceToTop < d->mScrollBorderWidth && distanceToTop > -d->mScrollBorderWidth ) { d->mScrollUpTimer.start( d->mScrollDelay ); } else if ( contentsY() + d->mScrollArea->viewport()->height() - d->mScrollBorderWidth < pos.y() ) { d->mScrollDownTimer.start( d->mScrollDelay ); } else { d->mScrollUpTimer.stop(); d->mScrollDownTimer.stop(); } // Move or resize item if necessary if ( d->mEndCell != gpos ) { if ( !d->mItemMoved ) { if ( !d->mChanger ) { KMessageBox::information( this, i18n( "Unable to lock item for modification. " "You cannot make any changes." ), i18n( "Locking Failed" ), QLatin1String("AgendaLockingFailed") ); d->mScrollUpTimer.stop(); d->mScrollDownTimer.stop(); d->mActionItem->resetMove(); placeSubCells( d->mActionItem ); #ifndef QT_NO_CURSOR setCursor( Qt::ArrowCursor ); #endif d->mActionItem = 0; d->mActionType = NOP; d->mItemMoved = false; return; } d->mItemMoved = true; } d->mActionItem->raise(); if ( d->mActionType == MOVE ) { // Move all items belonging to a multi item AgendaItem::QPtr firstItem = d->mActionItem->firstMultiItem(); if ( !firstItem ) { firstItem = d->mActionItem; } AgendaItem::QPtr lastItem = d->mActionItem->lastMultiItem(); if ( !lastItem ) { lastItem = d->mActionItem; } QPoint deltapos = gpos - d->mEndCell; AgendaItem::QPtr moveItem = firstItem; while ( moveItem ) { bool changed = false; if ( deltapos.x() != 0 ) { moveItem->moveRelative( deltapos.x(), 0 ); changed = true; } // in all day view don't try to move multi items, since there are none if ( moveItem == firstItem && !d->mAllDayMode ) { // is the first item int newY = deltapos.y() + moveItem->cellYTop(); // If event start moved earlier than 0:00, it starts the previous day if ( newY < 0 && newY > d->mScrollBorderWidth ) { moveItem->expandTop( -moveItem->cellYTop() ); // prepend a new item at ( x-1, rows()+newY to rows() ) AgendaItem::QPtr newFirst = firstItem->prevMoveItem(); // cell's y values are first and last cell of the bar, // so if newY=-1, they need to be the same if ( newFirst ) { newFirst->setCellXY( moveItem->cellXLeft() - 1, rows() + newY, rows() - 1 ); d->mItems.append( newFirst ); moveItem->resize( int( d->mGridSpacingX * newFirst->cellWidth() ), int( d->mGridSpacingY * newFirst->cellHeight() ) ); QPoint cpos = gridToContents( QPoint( newFirst->cellXLeft(), newFirst->cellYTop() ) ); newFirst->setParent( this ); newFirst->move( cpos.x(), cpos.y() ); } else { newFirst = insertItem( moveItem->incidence(), moveItem->occurrenceDateTime(), moveItem->cellXLeft() - 1, rows() + newY, rows() - 1, moveItem->itemPos(), moveItem->itemCount(), false ) ; } if ( newFirst ) { newFirst->show(); } moveItem->prependMoveItem( newFirst ); firstItem = newFirst; } else if ( newY >= rows() ) { // If event start is moved past 24:00, it starts the next day // erase current item (i.e. remove it from the multiItem list) firstItem = moveItem->nextMultiItem(); moveItem->hide(); d->mItems.removeAll( moveItem ); // removeChild( moveItem ); d->mActionItem->removeMoveItem( moveItem ); moveItem=firstItem; // adjust next day's item if ( moveItem ) { moveItem->expandTop( rows() - newY ); } } else { moveItem->expandTop( deltapos.y(), true ); } changed=true; } if ( moveItem && !moveItem->lastMultiItem() && !d->mAllDayMode ) { // is the last item int newY = deltapos.y() + moveItem->cellYBottom(); if ( newY < 0 ) { // erase current item lastItem = moveItem->prevMultiItem(); moveItem->hide(); d->mItems.removeAll( moveItem ); // removeChild( moveItem ); moveItem->removeMoveItem( moveItem ); moveItem = lastItem; moveItem->expandBottom( newY + 1 ); } else if ( newY >= rows() ) { moveItem->expandBottom( rows()-moveItem->cellYBottom() - 1 ); // append item at ( x+1, 0 to newY-rows() ) AgendaItem::QPtr newLast = lastItem->nextMoveItem(); if ( newLast ) { newLast->setCellXY( moveItem->cellXLeft() + 1, 0, newY-rows() - 1 ); d->mItems.append( newLast ); moveItem->resize( int( d->mGridSpacingX * newLast->cellWidth() ), int( d->mGridSpacingY * newLast->cellHeight() ) ); QPoint cpos = gridToContents( QPoint( newLast->cellXLeft(), newLast->cellYTop() ) ) ; newLast->setParent( this ); newLast->move( cpos.x(), cpos.y() ); } else { newLast = insertItem( moveItem->incidence(), moveItem->occurrenceDateTime(), moveItem->cellXLeft() + 1, 0, newY - rows() - 1, moveItem->itemPos(), moveItem->itemCount(), false ) ; } moveItem->appendMoveItem( newLast ); newLast->show(); lastItem = newLast; } else { moveItem->expandBottom( deltapos.y() ); } changed = true; } if ( changed ) { adjustItemPosition( moveItem ); } if ( moveItem ) { moveItem = moveItem->nextMultiItem(); } } } else if ( d->mActionType == RESIZETOP ) { if ( d->mEndCell.y() <= d->mActionItem->cellYBottom() ) { d->mActionItem->expandTop( gpos.y() - d->mEndCell.y() ); adjustItemPosition( d->mActionItem ); } } else if ( d->mActionType == RESIZEBOTTOM ) { if ( d->mEndCell.y() >= d->mActionItem->cellYTop() ) { d->mActionItem->expandBottom( gpos.y() - d->mEndCell.y() ); adjustItemPosition( d->mActionItem ); } } else if ( d->mActionType == RESIZELEFT ) { if ( d->mEndCell.x() <= d->mActionItem->cellXRight() ) { d->mActionItem->expandLeft( gpos.x() - d->mEndCell.x() ); adjustItemPosition( d->mActionItem ); } } else if ( d->mActionType == RESIZERIGHT ) { if ( d->mEndCell.x() >= d->mActionItem->cellXLeft() ) { d->mActionItem->expandRight( gpos.x() - d->mEndCell.x() ); adjustItemPosition( d->mActionItem ); } } d->mEndCell = gpos; } } void Agenda::endItemAction() { //PENDING(AKONADI_PORT) review all this cloning and changer calls d->mActionType = NOP; d->mScrollUpTimer.stop(); d->mScrollDownTimer.stop(); #ifndef QT_NO_CURSOR setCursor( Qt::ArrowCursor ); #endif if ( !d->mChanger ) { kError() << "No IncidenceChanger set"; return; } bool multiModify = false; // FIXME: do the cloning here... KCalCore::Incidence::Ptr incidence = d->mActionItem->incidence(); const KDateTime recurrenceId = d->mActionItem->occurrenceDateTime(); d->mItemMoved = d->mItemMoved && !( d->mStartCell.x() == d->mEndCell.x() && d->mStartCell.y() == d->mEndCell.y() ); bool addIncidence = false; if ( d->mItemMoved ) { bool modify = false; //get the main event and not the exception if (incidence->hasRecurrenceId() && !incidence->recurs()) { - KCalCore::Incidence::Ptr mainIncidence; - KCalCore::Calendar::Ptr cal = d->mCalendar->findCalendar(incidence)->getCalendar(); - if (CalendarSupport::hasEvent(incidence)) { - mainIncidence = cal->event(incidence->uid()); - } else if (CalendarSupport::hasTodo(incidence)) { - mainIncidence = cal->todo(incidence->uid()); - } + QSharedPointer cal = d->mCalendar->findCalendar(incidence)->getCalendar().staticCast(); + KCalCore::Incidence::Ptr mainIncidence = cal->incidence(cal->calendar(incidence), incidence->uid()); incidence = mainIncidence; } Akonadi::Item item = d->mCalendar->item(incidence); if ( incidence->recurs()) { const int res = d->mAgendaView->showMoveRecurDialog( incidence, recurrenceId.date() ); switch ( res ) { case KCalUtils::RecurrenceActions::AllOccurrences: // All occurrences // Moving the whole sequene of events is handled by the itemModified below. modify = true; break; case KCalUtils::RecurrenceActions::SelectedOccurrence: case KCalUtils::RecurrenceActions::FutureOccurrences: { const bool thisAndFuture = (res == KCalUtils::RecurrenceActions::FutureOccurrences); modify = true; multiModify = true; d->mChanger->startAtomicOperation( i18n( "Dissociate event from recurrence" ) ); KCalCore::Incidence::Ptr newInc( KCalCore::Calendar::createException( incidence, recurrenceId, thisAndFuture ) ); if ( newInc ) { newInc->removeCustomProperty("VOLATILE", "AKONADI-ID"); Akonadi::Item newItem = d->mCalendar->item(newInc); if (newItem.isValid() && newItem != item ) { //it is not a new exception item = newItem; newInc->setCustomProperty("VOLATILE", "AKONADI-ID", QString::number(newItem.id())); addIncidence = false; } else { addIncidence = true; } // don't recreate items, they already have the correct position d->mAgendaView->enableAgendaUpdate( false ); d->mActionItem->setIncidence( newInc ); d->mActionItem->dissociateFromMultiItem(); d->mAgendaView->enableAgendaUpdate( true ); } else { KMessageBox::sorry( this, i18n( "Unable to add the exception item to the calendar. " "No change will be done." ), i18n( "Error Occurred" ) ); } break; } default: modify = false; d->mActionItem->resetMove(); placeSubCells( d->mActionItem ); //PENDING(AKONADI_PORT) should this be done after //the new item was asynchronously added? } } AgendaItem::QPtr placeItem = d->mActionItem->firstMultiItem(); if ( !placeItem ) { placeItem = d->mActionItem; } Akonadi::Collection::Id saveCollection = -1; if ( item.isValid()) { saveCollection = item.parentCollection().id(); // if parent collection is only a search collection for example if (!(item.parentCollection().rights() & Akonadi::Collection::CanCreateItem)) { saveCollection = item.storageCollectionId(); } } if ( modify ) { d->mActionItem->endMove(); AgendaItem::QPtr modif = placeItem; QList oldconflictItems = placeItem->conflictItems(); QList::iterator it; for ( it = oldconflictItems.begin(); it != oldconflictItems.end(); ++it ) { if ( *it ) { placeSubCells( *it ); } } while ( placeItem ) { placeSubCells( placeItem ); placeItem = placeItem->nextMultiItem(); } // Notify about change // The agenda view will apply the changes to the actual Incidence*! // Bug #228696 don't call endChanged now it's async in Akonadi so it can // be called before that modified item was done. And endChange is // calling when we move item. // Not perfect need to improve it! //mChanger->endChange( inc ); if (item.isValid()) { d->mAgendaView->updateEventDates( modif, addIncidence, saveCollection ); } if ( addIncidence ) { // delete the one we dragged, there's a new one being added async, due to dissociation. delete modif; } } else { // the item was moved, but not further modified, since it's not recurring // make sure the view updates anyhow, with the right item if (item.isValid()) { d->mAgendaView->updateEventDates( placeItem, addIncidence, saveCollection ); } } } d->mActionItem = 0; d->mItemMoved = false; if ( multiModify ) { d->mChanger->endAtomicOperation(); } } void Agenda::setActionCursor( int actionType, bool acting ) { #ifndef QT_NO_CURSOR switch ( actionType ) { case MOVE: if ( acting ) { setCursor( Qt::SizeAllCursor ); } else { setCursor( Qt::ArrowCursor ); } break; case RESIZETOP: case RESIZEBOTTOM: setCursor( Qt::SizeVerCursor ); break; case RESIZELEFT: case RESIZERIGHT: setCursor( Qt::SizeHorCursor ); break; default: setCursor( Qt::ArrowCursor ); } #endif } void Agenda::setNoActionCursor( AgendaItem::QPtr moveItem, const QPoint &pos ) { const KCalCore::Incidence::Ptr item = moveItem ? moveItem->incidence() : KCalCore::Incidence::Ptr(); const bool noResize = CalendarSupport::hasTodo( item ); Agenda::MouseActionType resizeType = MOVE; if ( !noResize ) { resizeType = isInResizeArea( d->mAllDayMode, pos, moveItem ); } setActionCursor( resizeType ); } /** calculate the width of the column subcells of the given item */ double Agenda::calcSubCellWidth( AgendaItem::QPtr item ) { QPoint pt, pt1; pt = gridToContents( QPoint( item->cellXLeft(), item->cellYTop() ) ); pt1 = gridToContents( QPoint( item->cellXLeft(), item->cellYTop() ) + QPoint( 1, 1 ) ); pt1 -= pt; int maxSubCells = item->subCells(); double newSubCellWidth; if ( d->mAllDayMode ) { newSubCellWidth = static_cast( pt1.y() ) / maxSubCells; } else { newSubCellWidth = static_cast( pt1.x() ) / maxSubCells; } return newSubCellWidth; } void Agenda::adjustItemPosition( AgendaItem::QPtr item ) { if ( !item ) { return; } item->resize( int( d->mGridSpacingX * item->cellWidth() ), int( d->mGridSpacingY * item->cellHeight() ) ); int clXLeft = item->cellXLeft(); if ( QApplication::isRightToLeft() ) { clXLeft = item->cellXRight() + 1; } QPoint cpos = gridToContents( QPoint( clXLeft, item->cellYTop() ) ); item->move( cpos.x(), cpos.y() ); } void Agenda::placeAgendaItem( AgendaItem::QPtr item, double subCellWidth ) { // "left" upper corner, no subcells yet, RTL layouts have right/left // switched, widths are negative then QPoint pt = gridToContents( QPoint( item->cellXLeft(), item->cellYTop() ) ); // right lower corner QPoint pt1 = gridToContents( QPoint( item->cellXLeft() + item->cellWidth(), item->cellYBottom() + 1 ) ); double subCellPos = item->subCell() * subCellWidth; // we need to add 0.01 to make sure we don't loose one pixed due to numerics // (i.e. if it would be x.9998, we want the integer, not rounded down. double delta = 0.01; if ( subCellWidth < 0 ) { delta = -delta; } int height, width, xpos, ypos; if ( d->mAllDayMode ) { width = pt1.x() - pt.x(); height = int( subCellPos + subCellWidth + delta ) - int( subCellPos ); xpos = pt.x(); ypos = pt.y() + int( subCellPos ); } else { width = int( subCellPos + subCellWidth + delta ) - int( subCellPos ); height = pt1.y() - pt.y(); xpos = pt.x() + int( subCellPos ); ypos = pt.y(); } if ( QApplication::isRightToLeft() ) { // RTL language/layout xpos += width; width = -width; } if ( height < 0 ) { // BTT (bottom-to-top) layout ?!? ypos += height; height = -height; } item->resize( width, height ); item->move( xpos, ypos ); } /* Place item in cell and take care that multiple items using the same cell do not overlap. This method is not yet optimal. It doesn't use the maximum space it can get in all cases. At the moment the method has a bug: When an item is placed only the sub cell widths of the items are changed, which are within the Y region the item to place spans. When the sub cell width change of one of this items affects a cell, where other items are, which do not overlap in Y with the item to place, the display gets corrupted, although the corruption looks quite nice. */ void Agenda::placeSubCells( AgendaItem::QPtr placeItem ) { #if 0 kDebug(); if ( placeItem ) { KCalCore::Incidence::Ptr event = placeItem->incidence(); if ( !event ) { kDebug() << " event is 0"; } else { kDebug() << " event:" << event->summary(); } } else { kDebug() << " placeItem is 0"; } kDebug() << "Agenda::placeSubCells()..."; #endif QList cells; foreach ( CalendarSupport::CellItem *item, d->mItems ) { if ( item ) { cells.append( item ); } } QList items = CalendarSupport::CellItem::placeItem( cells, placeItem ); placeItem->setConflictItems( QList() ); double newSubCellWidth = calcSubCellWidth( placeItem ); QList::iterator it; for ( it = items.begin(); it != items.end(); ++it ) { if ( *it ) { AgendaItem::QPtr item = static_cast( *it ); placeAgendaItem( item, newSubCellWidth ); item->addConflictItem( placeItem ); placeItem->addConflictItem( item ); } } if ( items.isEmpty() ) { placeAgendaItem( placeItem, newSubCellWidth ); } placeItem->update(); } int Agenda::columnWidth( int column ) const { int start = gridToContents( QPoint( column, 0 ) ).x(); if ( QApplication::isRightToLeft() ) { column--; } else { column++; } int end = gridToContents( QPoint( column, 0 ) ).x(); return end - start; } void Agenda::paintEvent( QPaintEvent * ) { QPainter p( this ); drawContents( &p, 0, -y(), d->mGridSpacingX * d->mColumns, d->mGridSpacingY * d->mRows + y() ); } /* Draw grid in the background of the agenda. */ void Agenda::drawContents( QPainter *p, int cx, int cy, int cw, int ch ) { QPixmap db( cw, ch ); db.fill(); // We don't want to see leftovers from previous paints QPainter dbp( &db ); // TODO: CHECK THIS // if ( ! d->preferences()->agendaGridBackgroundImage().isEmpty() ) { // QPixmap bgImage( d->preferences()->agendaGridBackgroundImage() ); // dbp.drawPixmap( 0, 0, cw, ch, bgImage ); FIXME // } dbp.fillRect( 0, 0, cw, ch, d->preferences()->agendaGridBackgroundColor() ); dbp.translate( -cx, -cy ); double lGridSpacingY = d->mGridSpacingY * 2; // If work day, use work color // If busy day, use busy color // if work and busy day, mix both, and busy color has alpha const QVector busyDayMask = d->mAgendaView->busyDayMask(); // Highlight working hours if ( d->mWorkingHoursEnable && d->mHolidayMask ) { const QColor workColor = d->preferences()->workingHoursColor(); QPoint pt1( cx, d->mWorkingHoursYTop ); QPoint pt2( cx + cw, d->mWorkingHoursYBottom ); if ( pt2.x() >= pt1.x() /*&& pt2.y() >= pt1.y()*/) { int gxStart = contentsToGrid( pt1 ).x(); int gxEnd = contentsToGrid( pt2 ).x(); // correct start/end for rtl layouts if ( gxStart > gxEnd ) { int tmp = gxStart; gxStart = gxEnd; gxEnd = tmp; } int xoffset = ( QApplication::isRightToLeft() ? 1 : 0 ); while ( gxStart <= gxEnd ) { int xStart = gridToContents( QPoint( gxStart + xoffset, 0 ) ).x(); int xWidth = columnWidth( gxStart ) + 1; if ( pt2.y() < pt1.y() ) { // overnight working hours if ( ( ( gxStart == 0 ) && !d->mHolidayMask->at( d->mHolidayMask->count() - 1 ) ) || ( ( gxStart > 0 ) && ( gxStart < int( d->mHolidayMask->count() ) ) && ( !d->mHolidayMask->at( gxStart - 1 ) ) ) ) { if ( pt2.y() > cy ) { dbp.fillRect( xStart, cy, xWidth, pt2.y() - cy + 1, workColor ); } } if ( ( gxStart < int( d->mHolidayMask->count() - 1 ) ) && ( !d->mHolidayMask->at( gxStart ) ) ) { if ( pt1.y() < cy + ch - 1 ) { dbp.fillRect( xStart, pt1.y(), xWidth, cy + ch - pt1.y() + 1, workColor ); } } } else { // last entry in holiday mask denotes the previous day not visible // (needed for overnight shifts) if ( gxStart < int( d->mHolidayMask->count() - 1 ) && !d->mHolidayMask->at( gxStart ) ) { dbp.fillRect( xStart, pt1.y(), xWidth, pt2.y() - pt1.y() + 1, workColor ); } } ++gxStart; } } } // busy days if ( d->preferences()->colorAgendaBusyDays() && !d->mAllDayMode ) { for ( int i = 0; i < busyDayMask.count(); ++i ) { if ( busyDayMask[i] ) { const QPoint pt1( cx + d->mGridSpacingX * i, 0 ); // const QPoint pt2( cx + mGridSpacingX * ( i+1 ), ch ); QColor busyColor = d->preferences()->viewBgBusyColor(); busyColor.setAlpha( EventViews::BUSY_BACKGROUND_ALPHA ); dbp.fillRect( pt1.x(), pt1.y(), d->mGridSpacingX, cy + ch, busyColor ); } } } // draw selection if ( d->mHasSelection && d->mAgendaView->dateRangeSelectionEnabled() ) { QPoint pt, pt1; if ( d->mSelectionEndCell.x() > d->mSelectionStartCell.x() ) { // multi day selection // draw start day pt = gridToContents( d->mSelectionStartCell ); pt1 = gridToContents( QPoint( d->mSelectionStartCell.x() + 1, d->mRows + 1 ) ); dbp.fillRect( QRect( pt, pt1 ), d->preferences()->agendaGridHighlightColor() ); // draw all other days between the start day and the day of the selection end for ( int c = d->mSelectionStartCell.x() + 1; c < d->mSelectionEndCell.x(); ++c ) { pt = gridToContents( QPoint( c, 0 ) ); pt1 = gridToContents( QPoint( c + 1, d->mRows + 1 ) ); dbp.fillRect( QRect( pt, pt1 ), d->preferences()->agendaGridHighlightColor() ); } // draw end day pt = gridToContents( QPoint( d->mSelectionEndCell.x(), 0 ) ); pt1 = gridToContents( d->mSelectionEndCell + QPoint( 1, 1 ) ); dbp.fillRect( QRect( pt, pt1 ), d->preferences()->agendaGridHighlightColor() ); } else { // single day selection pt = gridToContents( d->mSelectionStartCell ); pt1 = gridToContents( d->mSelectionEndCell + QPoint( 1, 1 ) ); dbp.fillRect( QRect( pt, pt1 ), d->preferences()->agendaGridHighlightColor() ); } } QPen hourPen( d->preferences()->agendaGridBackgroundColor().dark( 150 ) ); QPen halfHourPen( d->preferences()->agendaGridBackgroundColor().dark( 125 ) ); dbp.setPen( hourPen ); // Draw vertical lines of grid, start with the last line not yet visible double x = ( int( cx / d->mGridSpacingX ) ) * d->mGridSpacingX; while ( x < cx + cw ) { dbp.drawLine( int( x ), cy, int( x ), cy + ch ); x += d->mGridSpacingX; } // Draw horizontal lines of grid double y = ( int( cy / ( 2 * lGridSpacingY ) ) ) * 2 * lGridSpacingY; while ( y < cy + ch ) { dbp.drawLine( cx, int( y ), cx + cw, int( y ) ); y += 2 * lGridSpacingY; } y = ( 2 * int( cy / ( 2 * lGridSpacingY ) ) + 1 ) * lGridSpacingY; dbp.setPen( halfHourPen ); while ( y < cy + ch ) { dbp.drawLine( cx, int( y ), cx + cw, int( y ) ); y += 2 * lGridSpacingY; } p->drawPixmap( cx, cy, db ); } /* Convert srcollview contents coordinates to agenda grid coordinates. */ QPoint Agenda::contentsToGrid ( const QPoint &pos ) const { int gx = int( QApplication::isRightToLeft() ? d->mColumns - pos.x() / d->mGridSpacingX : pos.x() / d->mGridSpacingX ); int gy = int( pos.y() / d->mGridSpacingY ); return QPoint( gx, gy ); } /* Convert agenda grid coordinates to scrollview contents coordinates. */ QPoint Agenda::gridToContents( const QPoint &gpos ) const { int x = int( QApplication::isRightToLeft() ? ( d->mColumns - gpos.x() ) * d->mGridSpacingX : gpos.x() * d->mGridSpacingX ); int y = int( gpos.y() * d->mGridSpacingY ); return QPoint( x, y ); } /* Return Y coordinate corresponding to time. Coordinates are rounded to fit into the grid. */ int Agenda::timeToY( const QTime &time ) const { int minutesPerCell = 24 * 60 / d->mRows; int timeMinutes = time.hour() * 60 + time.minute(); int Y = ( timeMinutes + ( minutesPerCell / 2 ) ) / minutesPerCell; return Y; } /* Return time corresponding to cell y coordinate. Coordinates are rounded to fit into the grid. */ QTime Agenda::gyToTime( int gy ) const { int secondsPerCell = 24 * 60 * 60 / d->mRows; int timeSeconds = secondsPerCell * gy; QTime time( 0, 0, 0 ); if ( timeSeconds < 24 * 60 * 60 ) { time = time.addSecs(timeSeconds); } else { time.setHMS( 23, 59, 59 ); } return time; } QVector Agenda::minContentsY() const { QVector minArray; minArray.fill( timeToY( QTime( 23, 59 ) ), d->mSelectedDates.count() ); foreach ( AgendaItem::QPtr item, d->mItems ) { if ( item ) { int ymin = item->cellYTop(); int index = item->cellXLeft(); if ( index >= 0 && index < (int)( d->mSelectedDates.count() ) ) { if ( ymin < minArray[index] && !d->mItemsToDelete.contains( item ) ) { minArray[index] = ymin; } } } } return minArray; } QVector Agenda::maxContentsY() const { QVector maxArray; maxArray.fill( timeToY( QTime( 0, 0 ) ), d->mSelectedDates.count() ); foreach ( AgendaItem::QPtr item, d->mItems ) { if ( item ) { int ymax = item->cellYBottom(); int index = item->cellXLeft(); if ( index >= 0 && index < (int)( d->mSelectedDates.count() ) ) { if ( ymax > maxArray[index] && !d->mItemsToDelete.contains( item ) ) { maxArray[index] = ymax; } } } } return maxArray; } void Agenda::setStartTime( const QTime &startHour ) { const double startPos = ( startHour.hour() / 24. + startHour.minute() / 1440. + startHour.second() / 86400. ) * d->mRows * gridSpacingY(); verticalScrollBar()->setValue( startPos ); } /* Insert AgendaItem into agenda. */ AgendaItem::QPtr Agenda::insertItem( const KCalCore::Incidence::Ptr &incidence, const KDateTime &recurrenceId, int X, int YTop, int YBottom, int itemPos, int itemCount, bool isSelected ) { if ( d->mAllDayMode ) { kDebug() << "using this in all-day mode is illegal."; return 0; } d->mActionType = NOP; AgendaItem::QPtr agendaItem = createAgendaItem( incidence, itemPos, itemCount, recurrenceId, isSelected ); if ( !agendaItem ) { return AgendaItem::QPtr(); } if ( YBottom <= YTop ) { kDebug() << "Text:" << agendaItem->text() << " YSize<0"; YBottom = YTop; } agendaItem->resize( int( ( X + 1 ) * d->mGridSpacingX ) - int( X * d->mGridSpacingX ), int( YTop * d->mGridSpacingY ) - int( ( YBottom + 1 ) * d->mGridSpacingY ) ); agendaItem->setCellXY( X, YTop, YBottom ); agendaItem->setCellXRight( X ); agendaItem->setResourceColor(d->mCalendar->resourceColor(incidence)); agendaItem->installEventFilter( this ); agendaItem->move( int( X * d->mGridSpacingX ), int( YTop * d->mGridSpacingY ) ); d->mItems.append( agendaItem ); placeSubCells( agendaItem ); agendaItem->show(); marcus_bains(); return agendaItem; } /* Insert all-day AgendaItem into agenda. */ AgendaItem::QPtr Agenda::insertAllDayItem( const KCalCore::Incidence::Ptr &incidence, const KDateTime &recurrenceId, int XBegin, int XEnd, bool isSelected ) { if ( !d->mAllDayMode ) { kError() << "using this in non all-day mode is illegal."; return 0; } d->mActionType = NOP; AgendaItem::QPtr agendaItem = createAgendaItem( incidence, 1, 1, recurrenceId, isSelected ); if ( !agendaItem ) { return AgendaItem::QPtr(); } agendaItem->setCellXY( XBegin, 0, 0 ); agendaItem->setCellXRight( XEnd ); const double startIt = d->mGridSpacingX * ( agendaItem->cellXLeft() ); const double endIt = d->mGridSpacingX * ( agendaItem->cellWidth() + agendaItem->cellXLeft() ); agendaItem->resize( int( endIt ) - int( startIt ), int( d->mGridSpacingY ) ); agendaItem->installEventFilter( this ); agendaItem->setResourceColor(d->mCalendar->resourceColor(incidence)); agendaItem->move( int( XBegin * d->mGridSpacingX ), 0 ) ; d->mItems.append( agendaItem ); placeSubCells( agendaItem ); agendaItem->show(); return agendaItem; } AgendaItem::QPtr Agenda::createAgendaItem( const KCalCore::Incidence::Ptr &incidence, int itemPos, int itemCount, const KDateTime &recurrenceId, bool isSelected ) { if ( !incidence ) { kWarning() << "Agenda::createAgendaItem() item is invalid."; return AgendaItem::QPtr(); } AgendaItem::QPtr agendaItem = new AgendaItem( d->mAgendaView, d->mCalendar, incidence, itemPos, itemCount, recurrenceId, isSelected, this ); connect( agendaItem, SIGNAL(removeAgendaItem(AgendaItem::QPtr)), SLOT(removeAgendaItem(AgendaItem::QPtr)) ); connect( agendaItem, SIGNAL(showAgendaItem(AgendaItem::QPtr)), SLOT(showAgendaItem(AgendaItem::QPtr)) ); - d->mAgendaItemsById.insert( incidence->uid(), agendaItem ); + d->mAgendaItemsById.insert( incidence, agendaItem ); return agendaItem; } void Agenda::insertMultiItem( const KCalCore::Incidence::Ptr &event, const KDateTime &recurrenceId, int XBegin, int XEnd, int YTop, int YBottom, bool isSelected ) { KCalCore::Event::Ptr ev = CalendarSupport::event( event ); Q_ASSERT( ev ); if ( d->mAllDayMode ) { kDebug() << "using this in all-day mode is illegal."; return; } d->mActionType = NOP; int cellX, cellYTop, cellYBottom; QString newtext; int width = XEnd - XBegin + 1; int count = 0; AgendaItem::QPtr current = 0; QList multiItems; int visibleCount = d->mSelectedDates.first().daysTo( d->mSelectedDates.last() ); for ( cellX = XBegin; cellX <= XEnd; ++cellX ) { ++count; //Only add the items that are visible. if( cellX >=0 && cellX <= visibleCount ) { if ( cellX == XBegin ) { cellYTop = YTop; } else { cellYTop = 0; } if ( cellX == XEnd ) { cellYBottom = YBottom; } else { cellYBottom = rows() - 1; } newtext = QString::fromLatin1( "(%1/%2): " ).arg( count ).arg( width ); newtext.append( ev->summary() ); current = insertItem( event, recurrenceId, cellX, cellYTop, cellYBottom, count, width, isSelected ); Q_ASSERT( current ); current->setText( newtext ); multiItems.append( current ); } } QList::iterator it = multiItems.begin(); QList::iterator e = multiItems.end(); if ( it != e ) { // .first asserts if the list is empty AgendaItem::QPtr first = multiItems.first(); AgendaItem::QPtr last = multiItems.last(); AgendaItem::QPtr prev = 0, next = 0; while ( it != e ) { AgendaItem::QPtr item = *it; ++it; next = ( it == e ) ? 0 : (*it); if ( item ) { item->setMultiItem( ( item == first ) ? 0 : first, prev, next, ( item == last ) ? 0 : last ); } prev = item; } } marcus_bains(); } void Agenda::removeIncidence( const KCalCore::Incidence::Ptr &incidence ) { if ( !incidence ) { kWarning() << "Agenda::removeIncidence() incidence is invalid"; return; } - /* - if ( !d->mViewCalendar->item(incidence).isValid() ) { - // Ok, we really don't know the ID, give up. - kWarning() << "Agenda::removeIncidence() Item to remove is invalid. uid = " - << incidence->instanceIdentifier(); - return; - } */ - - if ( d->isQueuedForDeletion( incidence->uid() ) ) { + if ( d->isQueuedForDeletion( incidence ) ) { return; // It's already queued for deletion } - AgendaItem::List agendaItems = d->mAgendaItemsById.values( incidence->uid() ); + QList agendaItems = d->mAgendaItemsById.values(incidence); if ( agendaItems.isEmpty() ) { // We're not displaying such item // kDebug() << "Ignoring"; return; } - foreach ( const AgendaItem::QPtr &agendaItem, agendaItems ) { - if (agendaItem) { - if (incidence->instanceIdentifier() != agendaItem->incidence()->instanceIdentifier() ) { - continue; - } - if ( !removeAgendaItem( agendaItem ) ) { + Q_FOREACH (const AgendaItem::QPtr &agendaItem, agendaItems) { + if ( agendaItem && !removeAgendaItem( agendaItem ) ) { kWarning() << "Agenda::removeIncidence() Failed to remove " << incidence->uid(); - } } } } void Agenda::showAgendaItem( AgendaItem::QPtr agendaItem ) { if ( !agendaItem ) { kError() << "Show what?"; return; } agendaItem->hide(); agendaItem->setParent( this ); if ( !d->mItems.contains( agendaItem ) ) { d->mItems.append( agendaItem ); } placeSubCells( agendaItem ); agendaItem->show(); } bool Agenda::removeAgendaItem( AgendaItem::QPtr agendaItem ) { Q_ASSERT( agendaItem ); // we found the item. Let's remove it and update the conflicts bool taken = false; QList conflictItems = agendaItem->conflictItems(); // removeChild( thisItem ); taken = d->mItems.removeAll( agendaItem ) > 0; - d->mAgendaItemsById.remove( agendaItem->incidence()->uid(), agendaItem ); + d->mAgendaItemsById.remove( agendaItem->incidence(), agendaItem ); QList::iterator it; for ( it = conflictItems.begin(); it != conflictItems.end(); ++it ) { if ( *it ) { (*it)->setSubCells( ( *it )->subCells()-1 ); } } for ( it = conflictItems.begin(); it != conflictItems.end(); ++it ) { // the item itself is also in its own conflictItems list! if ( *it && *it != agendaItem ) { placeSubCells( *it ); } } d->mItemsToDelete.append( agendaItem ); - d->mItemsQueuedForDeletion.insert( agendaItem->incidence()->uid() ); + d->mItemsQueuedForDeletion.insert( agendaItem->incidence() ); agendaItem->setVisible( false ); QTimer::singleShot( 0, this, SLOT(deleteItemsToDelete()) ); return taken; } void Agenda::deleteItemsToDelete() { qDeleteAll( d->mItemsToDelete ); d->mItemsToDelete.clear(); d->mItemsQueuedForDeletion.clear(); } /*QSizePolicy Agenda::sizePolicy() const { // Thought this would make the all-day event agenda minimum size and the // normal agenda take the remaining space. But it doesn't work. The QSplitter // don't seem to think that an Expanding widget needs more space than a // Preferred one. // But it doesn't hurt, so it stays. if (mAllDayMode) { return QSizePolicy(QSizePolicy::Expanding,QSizePolicy::Preferred); } else { return QSizePolicy(QSizePolicy::Expanding,QSizePolicy::Expanding); } }*/ /* Overridden from QScrollView to provide proper resizing of AgendaItems. */ void Agenda::resizeEvent ( QResizeEvent *ev ) { QSize newSize( ev->size() ); if ( d->mAllDayMode ) { d->mGridSpacingX = static_cast( newSize.width() ) / d->mColumns; d->mGridSpacingY = newSize.height(); } else { d->mGridSpacingX = static_cast( newSize.width() ) / d->mColumns; // make sure that there are not more than 24 per day d->mGridSpacingY = static_cast( newSize.height() ) / d->mRows; if ( d->mGridSpacingY < d->mDesiredGridSpacingY ) { d->mGridSpacingY = d->mDesiredGridSpacingY; } } calculateWorkingHours(); QTimer::singleShot( 0, this, SLOT(resizeAllContents()) ); emit gridSpacingYChanged( d->mGridSpacingY * 4 ); QWidget::resizeEvent( ev ); updateGeometry(); } void Agenda::resizeAllContents() { double subCellWidth; if ( d->mAllDayMode ) { foreach ( const AgendaItem::QPtr &item, d->mItems ) { if ( item ) { subCellWidth = calcSubCellWidth( item ); placeAgendaItem( item, subCellWidth ); } } } else { foreach ( const AgendaItem::QPtr &item, d->mItems ) { if ( item ) { subCellWidth = calcSubCellWidth( item ); placeAgendaItem( item, subCellWidth ); } } } checkScrollBoundaries(); marcus_bains(); update(); } void Agenda::scrollUp() { int currentValue = verticalScrollBar()->value(); verticalScrollBar()->setValue( currentValue - d->mScrollOffset ); } void Agenda::scrollDown() { int currentValue = verticalScrollBar()->value(); verticalScrollBar()->setValue( currentValue + d->mScrollOffset ); } QSize Agenda::minimumSize() const { return sizeHint(); } QSize Agenda::minimumSizeHint() const { return sizeHint(); } int Agenda::minimumHeight() const { // all day agenda never has scrollbars and the scrollarea will // resize it to fit exactly on the viewport. if ( d->mAllDayMode ) { return 0; } else { return d->mGridSpacingY * d->mRows; } } void Agenda::updateConfig() { const double oldGridSpacingY = d->mGridSpacingY; if ( !d->mAllDayMode ) { d->mDesiredGridSpacingY = d->preferences()->hourSize(); if ( d->mDesiredGridSpacingY < 4 || d->mDesiredGridSpacingY > 30 ) { d->mDesiredGridSpacingY = 10; } /* // make sure that there are not more than 24 per day d->mGridSpacingY = static_cast( height() ) / d->mRows; if ( d->mGridSpacingY < d->mDesiredGridSpacingY || true) { d->mGridSpacingY = d->mDesiredGridSpacingY; } */ //can be two doubles equal?, it's better to compare them with an epsilon if ( fabs( oldGridSpacingY - d->mDesiredGridSpacingY ) > 0.1 ) { d->mGridSpacingY = d->mDesiredGridSpacingY; updateGeometry(); } } calculateWorkingHours(); marcus_bains(); } void Agenda::checkScrollBoundaries() { // Invalidate old values to force update d->mOldLowerScrollValue = -1; d->mOldUpperScrollValue = -1; checkScrollBoundaries( verticalScrollBar()->value() ); } void Agenda::checkScrollBoundaries( int v ) { int yMin = int( (v) / d->mGridSpacingY ); int yMax = int( ( v + d->mScrollArea->height() ) / d->mGridSpacingY ); if ( yMin != d->mOldLowerScrollValue ) { d->mOldLowerScrollValue = yMin; emit lowerYChanged( yMin ); } if ( yMax != d->mOldUpperScrollValue ) { d->mOldUpperScrollValue = yMax; emit upperYChanged( yMax ); } } int Agenda::visibleContentsYMin() const { int v = verticalScrollBar()->value(); return int( v / d->mGridSpacingY ); } int Agenda::visibleContentsYMax() const { int v = verticalScrollBar()->value(); return int( ( v + d->mScrollArea->height() ) / d->mGridSpacingY ); } void Agenda::deselectItem() { if ( d->mSelectedItem.isNull() ) { return; } const KCalCore::Incidence::Ptr selectedItem = d->mSelectedItem->incidence(); foreach ( AgendaItem::QPtr item, d->mItems ) { if ( item ) { const KCalCore::Incidence::Ptr itemInc = item->incidence(); - if( itemInc && selectedItem && itemInc->uid() == selectedItem->uid() ) { + if( itemInc && selectedItem && itemInc->instanceIdentifier() == selectedItem->instanceIdentifier() ) { item->select( false ); } } } d->mSelectedItem = 0; } void Agenda::selectItem( AgendaItem::QPtr item ) { if ( ( AgendaItem::QPtr )d->mSelectedItem == item ) { return; } deselectItem(); if ( item == 0 ) { emit incidenceSelected( KCalCore::Incidence::Ptr(), QDate() ); return; } d->mSelectedItem = item; d->mSelectedItem->select(); Q_ASSERT( d->mSelectedItem->incidence() ); - d->mSelectedId = d->mSelectedItem->incidence()->uid(); + d->mSelectedId = d->mCalendar->uid(d->mSelectedItem->incidence()); foreach ( AgendaItem::QPtr item, d->mItems ) { - if( item && item->incidence()->uid() == d->mSelectedId ) { + if( item && d->mCalendar->uid(item->incidence()) == d->mSelectedId ) { item->select(); } } emit incidenceSelected( d->mSelectedItem->incidence(), d->mSelectedItem->occurrenceDate() ); } -void Agenda::selectIncidenceByUid( const QString &uid ) +void Agenda::selectItem( const KCalCore::Incidence::Ptr &inc ) { - foreach ( AgendaItem::QPtr item, d->mItems ) { - if ( item && item->incidence()->uid() == uid ) { - selectItem( item ); + foreach ( AgendaItem::QPtr agendaItem, d->mItems ) { + if ( agendaItem && agendaItem->incidence()->uid() == inc->uid() ) { + selectItem( agendaItem ); break; } } } -void Agenda::selectItem( const Akonadi::Item &item ) -{ - selectIncidenceByUid( CalendarSupport::incidence(item)->uid() ); -} - // This function seems never be called. void Agenda::keyPressEvent( QKeyEvent *kev ) { switch( kev->key() ) { case Qt::Key_PageDown: verticalScrollBar()->triggerAction( QAbstractSlider::SliderPageStepAdd ); break; case Qt::Key_PageUp: verticalScrollBar()->triggerAction( QAbstractSlider::SliderPageStepSub ); break; case Qt::Key_Down: verticalScrollBar()->triggerAction( QAbstractSlider::SliderSingleStepAdd ); break; case Qt::Key_Up: verticalScrollBar()->triggerAction( QAbstractSlider::SliderSingleStepSub ); break; default: ; } } void Agenda::calculateWorkingHours() { d->mWorkingHoursEnable = !d->mAllDayMode; QTime tmp = d->preferences()->workingHoursStart().time(); d->mWorkingHoursYTop = int( 4 * d->mGridSpacingY * ( tmp.hour() + tmp.minute() / 60. + tmp.second() / 3600. ) ); tmp = d->preferences()->workingHoursEnd().time(); d->mWorkingHoursYBottom = int( 4 * d->mGridSpacingY * ( tmp.hour() + tmp.minute() / 60. + tmp.second() / 3600. ) - 1 ); } void Agenda::setDateList( const KCalCore::DateList &selectedDates ) { d->mSelectedDates = selectedDates; marcus_bains(); } KCalCore::DateList Agenda::dateList() const { return d->mSelectedDates; } void Agenda::setCalendar( const MultiViewCalendar::Ptr &cal ) { d->mCalendar = cal; } void Agenda::setIncidenceChanger( Akonadi::IncidenceChanger *changer ) { d->mChanger = changer; } void Agenda::setHolidayMask( QVector *mask ) { d->mHolidayMask = mask; } void Agenda::contentsMousePressEvent ( QMouseEvent *event ) { Q_UNUSED( event ); } QSize Agenda::sizeHint() const { if ( d->mAllDayMode ) { return QWidget::sizeHint(); } else { return QSize( parentWidget()->width(), d->mGridSpacingY * d->mRows ); } } QScrollBar * Agenda::verticalScrollBar() const { return d->mScrollArea->verticalScrollBar(); } QScrollArea *Agenda::scrollArea() const { return d->mScrollArea; } -AgendaItem::List Agenda::agendaItems( const QString &uid ) const +AgendaItem::List Agenda::agendaItems( const KCalCore::Incidence::Ptr &incidence ) const { - return d->mAgendaItemsById.values( uid ); + return d->mAgendaItemsById.values( incidence ); } AgendaScrollArea::AgendaScrollArea( bool isAllDay, AgendaView *agendaView, bool isInteractive, QWidget *parent ) : QScrollArea( parent ) { if ( isAllDay ) { mAgenda = new Agenda( agendaView, this, 1, isInteractive ); setVerticalScrollBarPolicy( Qt::ScrollBarAlwaysOff ); } else { mAgenda = new Agenda( agendaView, this, 1, 96, agendaView->preferences()->hourSize(), isInteractive ); } #ifdef KDEPIM_MOBILE_UI setVerticalScrollBarPolicy( Qt::ScrollBarAlwaysOff ); #endif setWidgetResizable( true ); setWidget( mAgenda ); setHorizontalScrollBarPolicy( Qt::ScrollBarAlwaysOff ); mAgenda->setStartTime( agendaView->preferences()->dayBegins().time() ); } AgendaScrollArea::~AgendaScrollArea() { } Agenda *AgendaScrollArea::agenda() const { return mAgenda; } // kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/calendarviews/agenda/agenda.h b/calendarviews/agenda/agenda.h index 081464787d..6a6cfb27cc 100644 --- a/calendarviews/agenda/agenda.h +++ b/calendarviews/agenda/agenda.h @@ -1,358 +1,355 @@ /* Copyright (c) 2001 Cornelius Schumacher Copyright (C) 2003-2004 Reinhold Kainhofer Copyright (C) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net Author: Kevin Krammer, krake@kdab.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #ifndef EVENTVIEWS_AGENDA_H #define EVENTVIEWS_AGENDA_H #include "eventviews_export.h" #include "agendaitem.h" #include "viewcalendar.h" #include #include #include #include namespace Akonadi { class IncidenceChanger; } namespace EventViews { class Agenda; class AgendaItem; class AgendaView; class EVENTVIEWS_EXPORT MarcusBains : public QFrame { Q_OBJECT public: explicit MarcusBains( EventView *eventView, Agenda *agenda = 0 ); void updateLocationRecalc( bool recalculate = false ); virtual ~MarcusBains(); public Q_SLOTS: void updateLocation(); private: class Private; Private *const d; }; class EVENTVIEWS_EXPORT Agenda : public QWidget { Q_OBJECT public: Agenda ( AgendaView *agendaView, QScrollArea *scrollArea, int columns, int rows, int rowSize, bool isInteractive ); Agenda ( AgendaView *agendaView, QScrollArea *scrollArea, int columns, bool isInteractive ); virtual ~Agenda(); KCalCore::Incidence::Ptr selectedIncidence() const; QDate selectedIncidenceDate() const; QSize sizeHint() const; QSize minimumSizeHint() const; QSize minimumSize() const; int minimumHeight() const; // QSizePolicy sizePolicy() const; int contentsY() const { return -y(); } int contentsX() const { return x(); } QScrollBar *verticalScrollBar() const; QScrollArea *scrollArea() const; - AgendaItem::List agendaItems( const QString &uid ) const; + AgendaItem::List agendaItems( const KCalCore::Incidence::Ptr &inc ) const; /** Returns the uid of the last incidence that was selected. This persists across reloads and clear, so that if the same uid reappears, it can be reselected. */ QString lastSelectedItemId() const; bool eventFilter ( QObject *, QEvent * ); void paintEvent( QPaintEvent * ); QPoint contentsToGrid ( const QPoint &pos ) const; QPoint gridToContents ( const QPoint &gpos ) const; int timeToY ( const QTime &time ) const; QTime gyToTime ( int y ) const; QVector minContentsY() const; QVector maxContentsY() const; int visibleContentsYMin() const; int visibleContentsYMax() const; void setStartTime( const QTime &startHour ); AgendaItem::QPtr insertItem ( const KCalCore::Incidence::Ptr &incidence, const KDateTime &recurrenceId, int X, int YTop, int YBottom, int itemPos, int itemCount, bool isSelected ); AgendaItem::QPtr insertAllDayItem ( const KCalCore::Incidence::Ptr &event, const KDateTime &recurrenceId, int XBegin, int XEnd, bool isSelected ); void insertMultiItem ( const KCalCore::Incidence::Ptr &event, const KDateTime &recurrenceId, int XBegin, int XEnd, int YTop, int YBottom, bool isSelected ); /** Removes an event and all its multi-items from the agenda. This function removes the items from the view, but doesn't delete them immediately. Instead, they are queued in mItemsToDelete and later deleted by the slot deleteItemsToDelete() (called by QTimer::singleShot ). @param incidence The pointer to the incidence that should be removed. */ void removeIncidence( const KCalCore::Incidence::Ptr &incidence ); void changeColumns( int columns ); int columns() const; int rows() const; double gridSpacingX() const; double gridSpacingY() const; void clear(); /** Update configuration from preference settings */ void updateConfig(); void checkScrollBoundaries(); void setHolidayMask( QVector * ); void setDateList( const KCalCore::DateList &selectedDates ); KCalCore::DateList dateList() const; void setCalendar( const EventViews::MultiViewCalendar::Ptr &cal ); void setIncidenceChanger( Akonadi::IncidenceChanger *changer ); public Q_SLOTS: void scrollUp(); void scrollDown(); void checkScrollBoundaries( int ); /** Deselect selected items. This function does not emit any signals. */ void deselectItem(); void clearSelection(); /** Select item. If the argument is 0, the currently selected item gets deselected. This function emits the itemSelected(bool) signal to inform about selection/deselection of events. */ void selectItem( AgendaItem::QPtr ); /** - Selects the item associated with a given Akonadi Item id. + Selects the item associated with a given incidence. Linear search, use carefully. - @param id the item id of the item that should be selected. If no such - item exists, the selection is not changed. */ - void selectIncidenceByUid( const QString &id ); - void selectItem( const Akonadi::Item &item ); + void selectItem( const KCalCore::Incidence::Ptr &inc ); bool removeAgendaItem( AgendaItem::QPtr item ); void showAgendaItem( AgendaItem::QPtr item ); Q_SIGNALS: void newEventSignal(); void newTimeSpanSignal( const QPoint &, const QPoint & ); void newStartSelectSignal(); void showIncidenceSignal( const KCalCore::Incidence::Ptr & ); void editIncidenceSignal( const KCalCore::Incidence::Ptr &, const KDateTime & ); void deleteIncidenceSignal( const KCalCore::Incidence::Ptr & ); void showIncidencePopupSignal( const KCalCore::Incidence::Ptr &, const QDate &); void showNewEventPopupSignal(); void incidenceSelected( const KCalCore::Incidence::Ptr &, const QDate & ); void lowerYChanged( int ); void upperYChanged( int ); void startDragSignal( const KCalCore::Incidence::Ptr & ); void droppedIncidences( const KCalCore::Incidence::List &, const QPoint &gpos, bool allDay ); void droppedIncidences( const QList &, const QPoint &gpos, bool allDay ); void enableAgendaUpdate( bool enable ); void zoomView( const int delta, const QPoint &pos, const Qt::Orientation ); void mousePosSignal( const QPoint &pos ); void enterAgenda(); void leaveAgenda(); void gridSpacingYChanged( double ); private: enum MouseActionType { NOP, MOVE, SELECT, RESIZETOP, RESIZEBOTTOM, RESIZELEFT, RESIZERIGHT }; AgendaItem::QPtr createAgendaItem( const KCalCore::Incidence::Ptr &item, int itemPos, int itemCount, const KDateTime &recurrentId, bool isSelected ); protected: /** Draw the background grid of the agenda. @p cw grid width @p ch grid height */ void drawContents( QPainter *p, int cx, int cy, int cw, int ch ); int columnWidth( int column ) const; virtual void resizeEvent ( QResizeEvent * ); /** Handles mouse events. Called from eventFilter */ virtual bool eventFilter_mouse ( QObject *, QMouseEvent * ); #ifndef QT_NO_WHEELEVENT /** Handles mousewheel events. Called from eventFilter */ virtual bool eventFilter_wheel ( QObject *, QWheelEvent * ); #endif /** Handles key events. Called from eventFilter */ virtual bool eventFilter_key ( QObject *, QKeyEvent * ); /** Handles drag and drop events. Called from eventFilter */ virtual bool eventFilter_drag( QObject *, QDropEvent * ); /** returns RESIZELEFT if pos is near the lower edge of the action item, RESIZERIGHT if pos is near the higher edge, and MOVE otherwise. If --reverse is used, RESIZELEFT still means resizing the beginning of the event, although that means moving to the right! horizontal is the same as mAllDayAgenda. @param horizontal Whether horizontal resizing is possible @param pos The current mouse position @param item The affected item */ MouseActionType isInResizeArea( bool horizontal, const QPoint &pos, AgendaItem::QPtr item ); /** Return whether the cell specified by the grid point belongs to the current select */ bool ptInSelection( const QPoint &gpos ) const; /** Start selecting time span. */ void startSelectAction( const QPoint &viewportPos ); /** Select time span. */ void performSelectAction( const QPoint &viewportPos ); /** Emd selecting time span. */ void endSelectAction( const QPoint &viewportPos ); /** Start moving/resizing agenda item */ void startItemAction( const QPoint &viewportPos ); /** Move/resize agenda item */ void performItemAction( const QPoint &viewportPos ); /** End moving/resizing agenda item */ void endItemAction(); /** Set cursor, when no item action is in progress */ void setNoActionCursor( AgendaItem::QPtr moveItem, const QPoint &viewportPos ); /** Sets the cursor according to the given action type. @param actionType The type of action for which the cursor should be set. @param acting If true, the corresponding action is running (e.g. the item is currently being moved by the user). If false the cursor should just indicate that the corresponding action is possible */ void setActionCursor( int actionType, bool acting=false ); /** calculate the width of the column subcells of the given item */ double calcSubCellWidth( AgendaItem::QPtr item ); /** Move and resize the given item to the correct position */ void placeAgendaItem( AgendaItem::QPtr item, double subCellWidth ); /** Place agenda item in agenda and adjust other cells if necessary */ void placeSubCells( AgendaItem::QPtr placeItem ); /** Place the agenda item at the correct position (ignoring conflicting items) */ void adjustItemPosition( AgendaItem::QPtr item ); /** Process the keyevent, including the ignored keyevents of eventwidgets. * Implements pgup/pgdn and cursor key navigation in the view. */ void keyPressEvent( QKeyEvent * ); void calculateWorkingHours(); virtual void contentsMousePressEvent ( QMouseEvent * ); protected Q_SLOTS: /** delete the items that are queued for deletion */ void deleteItemsToDelete(); /** Resizes all the child elements after the size of the agenda changed. This is needed because Qt seems to have a bug when the resizeEvent of one of the widgets in a splitter takes a lot of time / does a lot of resizes.... see bug 80114 */ void resizeAllContents(); private: void init(); void marcus_bains(); private: class Private; Private *const d; }; class EVENTVIEWS_EXPORT AgendaScrollArea : public QScrollArea { public: AgendaScrollArea( bool allDay, AgendaView *agendaView, bool isInteractive, QWidget *parent ); ~AgendaScrollArea(); Agenda *agenda() const; private: Agenda *mAgenda; }; } #endif // kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/calendarviews/agenda/agendaitem.cpp b/calendarviews/agenda/agendaitem.cpp index 5ef3f9b411..6842ea62ef 100644 --- a/calendarviews/agenda/agendaitem.cpp +++ b/calendarviews/agenda/agendaitem.cpp @@ -1,1371 +1,1370 @@ /* Copyright (c) 2000,2001,2003 Cornelius Schumacher Copyright (C) 2003-2004 Reinhold Kainhofer This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #include "agendaitem.h" #include "eventview.h" #include "viewcalendar.h" #include "helper.h" #include "prefs.h" #include "prefs_base.h" // for enums #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace KCalCore; using namespace EventViews; //----------------------------------------------------------------------------- QPixmap *AgendaItem::alarmPxmp = 0; QPixmap *AgendaItem::recurPxmp = 0; QPixmap *AgendaItem::readonlyPxmp = 0; QPixmap *AgendaItem::replyPxmp = 0; QPixmap *AgendaItem::groupPxmp = 0; QPixmap *AgendaItem::groupPxmpTent = 0; QPixmap *AgendaItem::organizerPxmp = 0; QPixmap *AgendaItem::eventPxmp = 0; //----------------------------------------------------------------------------- AgendaItem::AgendaItem( EventView *eventView, const MultiViewCalendar::Ptr &calendar, const KCalCore::Incidence::Ptr &item, int itemPos, int itemCount, const KDateTime &qd, bool isSelected, QWidget *parent ) : QWidget( parent ), mEventView( eventView ), mCalendar( calendar ), mIncidence( item ), mOccurrenceDateTime( qd ), mValid( true ), mCloned( false ), mSelected( isSelected ), mSpecialEvent( false ) { if ( !mIncidence ) { mValid = false; return; } - mIncidence = Incidence::Ptr(mIncidence->clone()); if ( mIncidence->customProperty( "KABC", "BIRTHDAY" ) == QLatin1String("YES") || mIncidence->customProperty( "KABC", "ANNIVERSARY" ) == QLatin1String("YES") ) { const int years = EventViews::yearDiff( mIncidence->dtStart().date(), qd.toTimeSpec( mEventView->preferences()->timeSpec() ).date() ); if ( years > 0 ) { mIncidence->setReadOnly( false ); mIncidence->setSummary( i18np( "%2 (1 year)", "%2 (%1 years)", years, mIncidence->summary() ) ); mIncidence->setReadOnly( true ); mCloned = true; } } mLabelText = mIncidence->summary(); mIconAlarm = false; mIconRecur = false; mIconReadonly = false; mIconReply = false; mIconGroup = false; mIconGroupTent = false; mIconOrganizer = false; mMultiItemInfo = 0; mStartMoveInfo = 0; mItemPos = itemPos; mItemCount = itemCount; QPalette pal = palette(); pal.setColor( QPalette::Window, Qt::transparent ); setPalette( pal ); setCellXY( 0, 0, 1 ); setCellXRight( 0 ); setMouseTracking( true ); mResourceColor = QColor(); updateIcons(); setAcceptDrops( true ); } AgendaItem::~AgendaItem() { } void AgendaItem::updateIcons() { if ( !mValid ) { return; } mIconReadonly = mIncidence->isReadOnly(); mIconRecur = mIncidence->recurs() || mIncidence->hasRecurrenceId(); mIconAlarm = mIncidence->hasEnabledAlarms(); if ( mIncidence->attendeeCount() > 1 ) { if ( mEventView->kcalPreferences()->thatIsMe( mIncidence->organizer()->email() ) ) { mIconReply = false; mIconGroup = false; mIconGroupTent = false; mIconOrganizer = true; } else { KCalCore::Attendee::Ptr me = mIncidence->attendeeByMails( mEventView->kcalPreferences()->allEmails() ); if ( me ) { if ( me->status() == KCalCore::Attendee::NeedsAction && me->RSVP() ) { mIconReply = true; mIconGroup = false; mIconGroupTent = false; mIconOrganizer = false; } else if ( me->status() == KCalCore::Attendee::Tentative ) { mIconReply = false; mIconGroup = false; mIconGroupTent = true; mIconOrganizer = false; } else { mIconReply = false; mIconGroup = true; mIconGroupTent = false; mIconOrganizer = false; } } else { mIconReply = false; mIconGroup = true; mIconGroupTent = false; mIconOrganizer = false; } } } update(); } void AgendaItem::select( bool selected ) { if ( mSelected != selected ) { mSelected = selected; update(); } } bool AgendaItem::dissociateFromMultiItem() { if ( !isMultiItem() ) { return false; } AgendaItem::QPtr firstItem = firstMultiItem(); if ( firstItem == this ) { firstItem = nextMultiItem(); } AgendaItem::QPtr lastItem = lastMultiItem(); if ( lastItem == this ) { lastItem = prevMultiItem(); } AgendaItem::QPtr prevItem = prevMultiItem(); AgendaItem::QPtr nextItem = nextMultiItem(); if ( prevItem ) { prevItem->setMultiItem( firstItem, prevItem->prevMultiItem(), nextItem, lastItem ); } if ( nextItem ) { nextItem->setMultiItem( firstItem, prevItem, nextItem->prevMultiItem(), lastItem ); } delete mMultiItemInfo; mMultiItemInfo = 0; return true; } void AgendaItem::setIncidence( const KCalCore::Incidence::Ptr &incidence ) { mValid = false; if ( incidence ) { mValid = true; mIncidence = incidence; mLabelText = mIncidence->summary(); updateIcons(); } } /* Return height of item in units of agenda cells */ int AgendaItem::cellHeight() const { return mCellYBottom - mCellYTop + 1; } /* Return height of item in units of agenda cells */ int AgendaItem::cellWidth() const { return mCellXRight - mCellXLeft + 1; } void AgendaItem::setOccurrenceDateTime(const KDateTime& qd) { mOccurrenceDateTime = qd; } QDate AgendaItem::occurrenceDate() const { return mOccurrenceDateTime.toTimeSpec( mEventView->preferences()->timeSpec() ).date(); } void AgendaItem::setCellXY( int X, int YTop, int YBottom ) { mCellXLeft = X; mCellYTop = YTop; mCellYBottom = YBottom; } void AgendaItem::setCellXRight( int XRight ) { mCellXRight = XRight; } void AgendaItem::setCellX( int XLeft, int XRight ) { mCellXLeft = XLeft; mCellXRight = XRight; } void AgendaItem::setCellY( int YTop, int YBottom ) { mCellYTop = YTop; mCellYBottom = YBottom; } void AgendaItem::setMultiItem( AgendaItem::QPtr first, AgendaItem::QPtr prev, AgendaItem::QPtr next, AgendaItem::QPtr last ) { if ( !mMultiItemInfo ) { mMultiItemInfo = new MultiItemInfo; } mMultiItemInfo->mFirstMultiItem = first; mMultiItemInfo->mPrevMultiItem = prev; mMultiItemInfo->mNextMultiItem = next; mMultiItemInfo->mLastMultiItem = last; } bool AgendaItem::isMultiItem() const { return mMultiItemInfo; } AgendaItem::QPtr AgendaItem::prependMoveItem( AgendaItem::QPtr e ) { if ( !e ) { return 0; } AgendaItem::QPtr first = 0, last = 0; if ( isMultiItem() ) { first = mMultiItemInfo->mFirstMultiItem; last = mMultiItemInfo->mLastMultiItem; } if ( !first ) { first = this; } if ( !last ) { last = this; } e->setMultiItem( 0, 0, first, last ); first->setMultiItem( e, e, first->nextMultiItem(), first->lastMultiItem() ); AgendaItem::QPtr tmp = first->nextMultiItem(); while ( tmp ) { tmp->setMultiItem( e, tmp->prevMultiItem(), tmp->nextMultiItem(), tmp->lastMultiItem() ); tmp = tmp->nextMultiItem(); } if ( mStartMoveInfo && !e->moveInfo() ) { e->mStartMoveInfo=new MultiItemInfo( *mStartMoveInfo ); // e->moveInfo()->mFirstMultiItem = moveInfo()->mFirstMultiItem; // e->moveInfo()->mLastMultiItem = moveInfo()->mLastMultiItem; e->moveInfo()->mPrevMultiItem = 0; e->moveInfo()->mNextMultiItem = first; } if ( first && first->moveInfo() ) { first->moveInfo()->mPrevMultiItem = e; } return e; } AgendaItem::QPtr AgendaItem::appendMoveItem( AgendaItem::QPtr e ) { if ( !e ) { return 0; } AgendaItem::QPtr first = 0, last = 0; if ( isMultiItem() ) { first = mMultiItemInfo->mFirstMultiItem; last = mMultiItemInfo->mLastMultiItem; } if ( !first ) { first = this; } if ( !last ) { last = this; } e->setMultiItem( first, last, 0, 0 ); AgendaItem::QPtr tmp = first; while ( tmp ) { tmp->setMultiItem( tmp->firstMultiItem(), tmp->prevMultiItem(), tmp->nextMultiItem(), e ); tmp = tmp->nextMultiItem(); } last->setMultiItem( last->firstMultiItem(), last->prevMultiItem(), e, e ); if ( mStartMoveInfo && !e->moveInfo() ) { e->mStartMoveInfo=new MultiItemInfo( *mStartMoveInfo ); // e->moveInfo()->mFirstMultiItem = moveInfo()->mFirstMultiItem; // e->moveInfo()->mLastMultiItem = moveInfo()->mLastMultiItem; e->moveInfo()->mPrevMultiItem = last; e->moveInfo()->mNextMultiItem = 0; } if ( last && last->moveInfo() ) { last->moveInfo()->mNextMultiItem = e; } return e; } AgendaItem::QPtr AgendaItem::removeMoveItem( AgendaItem::QPtr e ) { if ( isMultiItem() ) { AgendaItem::QPtr first = mMultiItemInfo->mFirstMultiItem; AgendaItem::QPtr next, prev; AgendaItem::QPtr last = mMultiItemInfo->mLastMultiItem; if ( !first ) { first = this; } if ( !last ) { last = this; } if ( first == e ) { first = first->nextMultiItem(); first->setMultiItem( 0, 0, first->nextMultiItem(), first->lastMultiItem() ); } if ( last == e ) { last = last->prevMultiItem(); last->setMultiItem( last->firstMultiItem(), last->prevMultiItem(), 0, 0 ); } AgendaItem::QPtr tmp = first; if ( first == last ) { delete mMultiItemInfo; tmp = 0; mMultiItemInfo = 0; } while ( tmp ) { next = tmp->nextMultiItem(); prev = tmp->prevMultiItem(); if ( e == next ) { next = next->nextMultiItem(); } if ( e == prev ) { prev = prev->prevMultiItem(); } tmp->setMultiItem( ( tmp == first ) ? 0 : first, ( tmp == prev ) ? 0 : prev, ( tmp == next ) ? 0 : next, ( tmp == last ) ? 0 : last ); tmp = tmp->nextMultiItem(); } } return e; } void AgendaItem::startMove() { AgendaItem::QPtr first = this; if ( isMultiItem() && mMultiItemInfo->mFirstMultiItem ) { first=mMultiItemInfo->mFirstMultiItem; } first->startMovePrivate(); } void AgendaItem::startMovePrivate() { mStartMoveInfo = new MultiItemInfo; mStartMoveInfo->mStartCellXLeft = mCellXLeft; mStartMoveInfo->mStartCellXRight = mCellXRight; mStartMoveInfo->mStartCellYTop = mCellYTop; mStartMoveInfo->mStartCellYBottom = mCellYBottom; if ( mMultiItemInfo ) { mStartMoveInfo->mFirstMultiItem = mMultiItemInfo->mFirstMultiItem; mStartMoveInfo->mLastMultiItem = mMultiItemInfo->mLastMultiItem; mStartMoveInfo->mPrevMultiItem = mMultiItemInfo->mPrevMultiItem; mStartMoveInfo->mNextMultiItem = mMultiItemInfo->mNextMultiItem; } else { mStartMoveInfo->mFirstMultiItem = 0; mStartMoveInfo->mLastMultiItem = 0; mStartMoveInfo->mPrevMultiItem = 0; mStartMoveInfo->mNextMultiItem = 0; } if ( isMultiItem() && mMultiItemInfo->mNextMultiItem ) { mMultiItemInfo->mNextMultiItem->startMovePrivate(); } } void AgendaItem::resetMove() { if ( mStartMoveInfo ) { if ( mStartMoveInfo->mFirstMultiItem ) { mStartMoveInfo->mFirstMultiItem->resetMovePrivate(); } else { resetMovePrivate(); } } } void AgendaItem::resetMovePrivate() { if ( mStartMoveInfo ) { mCellXLeft = mStartMoveInfo->mStartCellXLeft; mCellXRight = mStartMoveInfo->mStartCellXRight; mCellYTop = mStartMoveInfo->mStartCellYTop; mCellYBottom = mStartMoveInfo->mStartCellYBottom; // if we don't have mMultiItemInfo, the item didn't span two days before, // and wasn't moved over midnight, either, so we don't have to reset // anything. Otherwise, restore from mMoveItemInfo if ( mMultiItemInfo ) { // It was already a multi-day info mMultiItemInfo->mFirstMultiItem = mStartMoveInfo->mFirstMultiItem; mMultiItemInfo->mPrevMultiItem = mStartMoveInfo->mPrevMultiItem; mMultiItemInfo->mNextMultiItem = mStartMoveInfo->mNextMultiItem; mMultiItemInfo->mLastMultiItem = mStartMoveInfo->mLastMultiItem; if ( !mStartMoveInfo->mFirstMultiItem ) { // This was the first multi-item when the move started, delete all previous AgendaItem::QPtr toDel = mStartMoveInfo->mPrevMultiItem; AgendaItem::QPtr nowDel = 0; while ( toDel ) { nowDel = toDel; if ( nowDel->moveInfo() ) { toDel = nowDel->moveInfo()->mPrevMultiItem; } emit removeAgendaItem( nowDel ); } mMultiItemInfo->mFirstMultiItem = 0; mMultiItemInfo->mPrevMultiItem = 0; } if ( !mStartMoveInfo->mLastMultiItem ) { // This was the last multi-item when the move started, delete all next AgendaItem::QPtr toDel = mStartMoveInfo->mNextMultiItem; AgendaItem::QPtr nowDel = 0; while ( toDel ) { nowDel = toDel; if ( nowDel->moveInfo() ) { toDel=nowDel->moveInfo()->mNextMultiItem; } emit removeAgendaItem( nowDel ); } mMultiItemInfo->mLastMultiItem = 0; mMultiItemInfo->mNextMultiItem = 0; } if ( mStartMoveInfo->mFirstMultiItem == 0 && mStartMoveInfo->mLastMultiItem == 0 ) { // it was a single-day event before we started the move. delete mMultiItemInfo; mMultiItemInfo = 0; } } delete mStartMoveInfo; mStartMoveInfo = 0; } emit showAgendaItem( this ); if ( nextMultiItem() ) { nextMultiItem()->resetMovePrivate(); } } void AgendaItem::endMove() { AgendaItem::QPtr first = firstMultiItem(); if ( !first ) { first = this; } first->endMovePrivate(); } void AgendaItem::endMovePrivate() { if ( mStartMoveInfo ) { // if first, delete all previous if ( !firstMultiItem() || firstMultiItem() == this ) { AgendaItem::QPtr toDel = mStartMoveInfo->mPrevMultiItem; AgendaItem::QPtr nowDel = 0; while ( toDel ) { nowDel = toDel; if ( nowDel->moveInfo() ) { toDel=nowDel->moveInfo()->mPrevMultiItem; } emit removeAgendaItem( nowDel ); } } // if last, delete all next if ( !lastMultiItem() || lastMultiItem() == this ) { AgendaItem::QPtr toDel=mStartMoveInfo->mNextMultiItem; AgendaItem::QPtr nowDel = 0; while ( toDel ) { nowDel = toDel; if ( nowDel->moveInfo() ) { toDel=nowDel->moveInfo()->mNextMultiItem; } emit removeAgendaItem( nowDel ); } } // also delete the moving info delete mStartMoveInfo; mStartMoveInfo = 0; if ( nextMultiItem() ) { nextMultiItem()->endMovePrivate(); } } } void AgendaItem::moveRelative( int dx, int dy ) { int newXLeft = cellXLeft() + dx; int newXRight = cellXRight() + dx; int newYTop = cellYTop() + dy; int newYBottom = cellYBottom() + dy; setCellXY( newXLeft, newYTop, newYBottom ); setCellXRight( newXRight ); } void AgendaItem::expandTop( int dy, const bool allowOverLimit ) { int newYTop = cellYTop() + dy; int newYBottom = cellYBottom(); if ( newYTop > newYBottom && !allowOverLimit ) { newYTop = newYBottom; } setCellY( newYTop, newYBottom ); } void AgendaItem::expandBottom( int dy ) { int newYTop = cellYTop(); int newYBottom = cellYBottom() + dy; if ( newYBottom < newYTop ) { newYBottom = newYTop; } setCellY( newYTop, newYBottom ); } void AgendaItem::expandLeft( int dx ) { int newXLeft = cellXLeft() + dx; int newXRight = cellXRight(); if ( newXLeft > newXRight ) { newXLeft = newXRight; } setCellX( newXLeft, newXRight ); } void AgendaItem::expandRight( int dx ) { int newXLeft = cellXLeft(); int newXRight = cellXRight() + dx; if ( newXRight < newXLeft ) { newXRight = newXLeft; } setCellX( newXLeft, newXRight ); } void AgendaItem::dragEnterEvent( QDragEnterEvent *e ) { #ifndef KORG_NODND const QMimeData *md = e->mimeData(); if ( KCalUtils::ICalDrag::canDecode( md ) || KCalUtils::VCalDrag::canDecode( md ) ) { // TODO: Allow dragging events/todos onto other events to create a relation e->ignore(); return; } if ( KABC::VCardDrag::canDecode( md ) || md->hasText() ) { e->accept(); } else { e->ignore(); } #else Q_UNUSED( e ); #endif } void AgendaItem::addAttendee( const QString &newAttendee ) { if ( !mValid ) { return; } QString name, email; KPIMUtils::extractEmailAddressAndName( newAttendee, email, name ); if ( !( name.isEmpty() && email.isEmpty() ) ) { mIncidence->addAttendee( KCalCore::Attendee::Ptr( new KCalCore::Attendee( name, email ) ) ); KMessageBox::information( this, i18n( "Attendee \"%1\" added to the calendar item \"%2\"", KPIMUtils::normalizedAddress( name, email, QString() ), text() ), i18n( "Attendee added" ), QLatin1String("AttendeeDroppedAdded") ); } } void AgendaItem::dropEvent( QDropEvent *e ) { // TODO: Organize this better: First check for attachment // (not only file, also any other url!), then if it's a vcard, // otherwise check for attendees, then if the data is binary, // add a binary attachment. #ifndef KORG_NODND if ( !mValid ) { return; } const QMimeData *md = e->mimeData(); bool decoded = md->hasText(); QString text = md->text(); if ( decoded && text.startsWith( QLatin1String( "file:" ) ) ) { mIncidence->addAttachment( KCalCore::Attachment::Ptr( new KCalCore::Attachment( text ) ) ); return; } KABC::Addressee::List list; if ( KABC::VCardDrag::fromMimeData( md, list ) ) { Q_FOREACH ( const KABC::Addressee &addressee, list ) { QString em( addressee.fullEmail() ); if ( em.isEmpty() ) { em = addressee.realName(); } addAttendee( em ); } } #else Q_UNUSED( e ); #endif // KORG_NODND } QList &AgendaItem::conflictItems() { return mConflictItems; } void AgendaItem::setConflictItems( QList ci ) { mConflictItems = ci; QList::iterator it; for ( it = mConflictItems.begin(); it != mConflictItems.end(); ++it ) { (*it)->addConflictItem( this ); } } void AgendaItem::addConflictItem( AgendaItem::QPtr ci ) { if ( !mConflictItems.contains( ci ) ) { mConflictItems.append( ci ); } } QString AgendaItem::label() const { return mLabelText; } bool AgendaItem::overlaps( CellItem *o ) const { AgendaItem::QPtr other = static_cast( o ); if ( cellXLeft() <= other->cellXRight() && cellXRight() >= other->cellXLeft() ) { if ( ( cellYTop() <= other->cellYBottom() ) && ( cellYBottom() >= other->cellYTop() ) ) { return true; } } return false; } static void conditionalPaint( QPainter *p, bool condition, int &x, int y, int ft, const QPixmap &pxmp ) { if ( condition ) { p->drawPixmap( x, y, pxmp ); x += pxmp.width() + ft; } } void AgendaItem::paintIcon( QPainter *p, int &x, int y, int ft ) { QString iconName; if ( mIncidence->customProperty( "KABC", "ANNIVERSARY" ) == QLatin1String("YES") ) { mSpecialEvent = true; iconName = QLatin1String("view-calendar-wedding-anniversary"); } else if ( mIncidence->customProperty( "KABC", "BIRTHDAY" ) == QLatin1String("YES") ) { mSpecialEvent = true; // We don't draw icon. The icon is drawn already, because it's the Akonadi::Collection's icon } conditionalPaint( p, !iconName.isEmpty(), x, y, ft, cachedSmallIcon( iconName ) ); } void AgendaItem::paintIcons( QPainter *p, int &x, int y, int ft ) { if ( !mEventView->preferences()->enableAgendaItemIcons() ) { return; } paintIcon( p, x, y, ft ); QSet icons = mEventView->preferences()->agendaViewIcons(); if ( icons.contains( EventViews::EventView::CalendarCustomIcon ) ) { const QString iconName = mCalendar->iconForIncidence( mIncidence ); if ( !iconName.isEmpty() && iconName != QLatin1String("view-calendar") && iconName != QLatin1String("office-calendar") ) { conditionalPaint( p, true, x, y, ft, SmallIcon( iconName ) ); } } const bool isTodo = mIncidence && mIncidence->type() == Incidence::TypeTodo; if ( isTodo && icons.contains( EventViews::EventView::TaskIcon ) ) { const QString iconName = mIncidence->iconName( mOccurrenceDateTime.toTimeSpec( mIncidence->dtStart().timeSpec() ) ); conditionalPaint( p, !mSpecialEvent, x, y, ft, SmallIcon( iconName ) ); } if ( icons.contains( EventView::RecurringIcon ) ) { conditionalPaint( p, mIconRecur && !mSpecialEvent, x, y, ft, *recurPxmp ); } if ( icons.contains( EventView::ReminderIcon ) ) { conditionalPaint( p, mIconAlarm && !mSpecialEvent, x, y, ft, *alarmPxmp ); } if ( icons.contains( EventView::ReadOnlyIcon ) ) { conditionalPaint( p, mIconReadonly && !mSpecialEvent, x, y, ft, *readonlyPxmp ); } if ( icons.contains( EventView::ReplyIcon ) ) { conditionalPaint( p, mIconReply, x, y, ft, *replyPxmp ); } if ( icons.contains( EventView::AttendingIcon ) ) { conditionalPaint( p, mIconGroup, x, y, ft, *groupPxmp ); } if ( icons.contains( EventView::TentativeIcon ) ) { conditionalPaint( p, mIconGroupTent, x, y, ft, *groupPxmpTent ); } if ( icons.contains( EventView::OrganizerIcon ) ) { conditionalPaint( p, mIconOrganizer, x, y, ft, *organizerPxmp ); } } void AgendaItem::paintEvent( QPaintEvent *ev ) { if ( !mValid ) { return; } QRect visRect = visibleRegion().boundingRect(); // when scrolling horizontally in the side-by-side view, the repainted area is clipped // to the newly visible area, which is a problem since the content changes when visRect // changes, so repaint the full item in that case if ( ev->rect() != visRect && visRect.isValid() && ev->rect().isValid() ) { update( visRect ); return; } QPainter p( this ); p.setRenderHint( QPainter::Antialiasing ); const int fmargin = 0; // frame margin const int ft = 1; // frame thickness for layout, see drawRoundedRect(), // keep multiple of 2 const int margin = 5 + ft + fmargin ; // frame + space between frame and content // General idea is to always show the icons (even in the all-day events). // This creates a consistent feeling for the user when the view mode // changes and therefore the available width changes. // Also look at #17984 if ( !alarmPxmp ) { alarmPxmp = new QPixmap( SmallIcon( QLatin1String("task-reminder") ) ); recurPxmp = new QPixmap( SmallIcon( QLatin1String("appointment-recurring") ) ); readonlyPxmp = new QPixmap( SmallIcon( QLatin1String("object-locked") ) ); replyPxmp = new QPixmap( SmallIcon( QLatin1String("mail-reply-sender") ) ); groupPxmp = new QPixmap( SmallIcon( QLatin1String("meeting-attending") ) ); groupPxmpTent = new QPixmap( SmallIcon( QLatin1String("meeting-attending-tentative") ) ); organizerPxmp = new QPixmap( SmallIcon( QLatin1String("meeting-organizer") ) ); } QColor bgColor; if ( CalendarSupport::hasTodo( mIncidence ) && !mEventView->preferences()->todosUseCategoryColors() ) { Todo::Ptr todo = CalendarSupport::todo( mIncidence ); Q_ASSERT( todo ); const QDate dueDate = todo->dtDue().toTimeSpec( CalendarSupport::KCalPrefs::instance()->timeSpec() ).date(); const QDate today = KDateTime::currentDateTime( CalendarSupport::KCalPrefs::instance()->timeSpec() ).date(); const QDate occurrenceDate = this->occurrenceDate(); if ( todo->isOverdue() && today >= occurrenceDate ) { bgColor = mEventView->preferences()->todoOverdueColor(); } else if ( dueDate == today && dueDate == occurrenceDate ) { bgColor = mEventView->preferences()->todoDueTodayColor(); } } QColor categoryColor; const QStringList categories = mIncidence->categories(); QString cat; if ( !categories.isEmpty() ) { cat = categories.first(); } categoryColor = cat.isEmpty() ? CalendarSupport::KCalPrefs::instance()->unsetCategoryColor() : CalendarSupport::KCalPrefs::instance()->categoryColor( cat ); QColor resourceColor = mResourceColor; if ( !resourceColor.isValid() ) { resourceColor = categoryColor; } QColor frameColor; // TODO PrefsBase enums should probably be redefined in Prefs if ( mEventView->preferences()->agendaViewColors() == PrefsBase::ResourceOnly || mEventView->preferences()->agendaViewColors() == PrefsBase::CategoryInsideResourceOutside ) { frameColor = bgColor.isValid() ? bgColor : resourceColor; } else { frameColor = bgColor.isValid() ? bgColor : categoryColor; } if ( !bgColor.isValid() ) { if ( mEventView->preferences()->agendaViewColors() == PrefsBase::ResourceOnly || mEventView->preferences()->agendaViewColors() == PrefsBase::ResourceInsideCategoryOutside ) { bgColor = resourceColor; } else { bgColor = categoryColor; } } if ( cat.isEmpty() && mEventView->preferences()->agendaViewColors() == PrefsBase::ResourceInsideCategoryOutside ) { frameColor = bgColor; } if ( cat.isEmpty() && mEventView->preferences()->agendaViewColors() == PrefsBase::CategoryInsideResourceOutside ) { bgColor = frameColor; } frameColor = EventView::itemFrameColor( frameColor, mSelected ); if ( !CalendarSupport::KCalPrefs::instance()->hasCategoryColor( cat ) ) { categoryColor = resourceColor; } if ( !bgColor.isValid() ) { bgColor = categoryColor; } if ( mSelected ) { bgColor = bgColor.light( EventView::BRIGHTNESS_FACTOR ); } const QColor textColor = EventViews::getTextColor( bgColor ); p.setPen( textColor ); p.setFont( mEventView->preferences()->agendaViewFont() ); QFontMetrics fm = p.fontMetrics(); const int singleLineHeight = fm.boundingRect( mLabelText ).height(); const bool roundTop = !prevMultiItem(); const bool roundBottom = !nextMultiItem(); drawRoundedRect( &p, QRect( fmargin, fmargin, width() - fmargin * 2, height() - fmargin * 2 ), mSelected, bgColor, true, ft, roundTop, roundBottom ); // calculate the height of the full version (case 4) to test whether it is // possible QString shortH; QString longH; if ( !isMultiItem() ) { shortH = KGlobal::locale()->formatTime(mIncidence->dateTime( KCalCore::Incidence::RoleDisplayStart ). toTimeSpec( mEventView->preferences()->timeSpec() ).time() ); if ( CalendarSupport::hasEvent( mIncidence ) ) { longH = i18n( "%1 - %2", shortH, KGlobal::locale()->formatTime( mIncidence->dateTime( KCalCore::Incidence::RoleEnd ).toTimeSpec( mEventView->preferences()->timeSpec() ).time() ) ); } else { longH = shortH; } } else if ( !mMultiItemInfo->mFirstMultiItem ) { shortH = KGlobal::locale()->formatTime( mIncidence->dtStart().toTimeSpec( mEventView->preferences()->timeSpec() ).time() ); longH = shortH; } else { shortH = KGlobal::locale()->formatTime( mIncidence->dateTime( KCalCore::Incidence::RoleEnd ).toTimeSpec( mEventView->preferences()->timeSpec() ).time() ); longH = i18n( "- %1", shortH ); } KWordWrap *ww = KWordWrap::formatText( fm, QRect( 0, 0, width() - ( 2 * margin ), -1 ), 0, mLabelText ); int th = ww->boundingRect().height(); delete ww; int hlHeight = qMax( fm.boundingRect( longH ).height(), qMax( alarmPxmp->height(), qMax( recurPxmp->height(), qMax( readonlyPxmp->height(), qMax( replyPxmp->height(), qMax( groupPxmp->height(), organizerPxmp->height() ) ) ) ) ) ); const bool completelyRenderable = th < ( height() - 2 * ft - 2 - hlHeight ); // case 1: do not draw text when not even a single line fits // Don't do this any more, always try to print out the text. // Even if it's just a few pixel, one can still guess the whole // text from just four pixels' height! if ( //( singleLineHeight > height() - 4 ) || ( width() < 16 ) ) { int x = qRound( ( width() - 16 ) / 2.0 ); paintIcon( &p, x/*by-ref*/, margin, ft ); return; } // case 2: draw a single line when no more space if ( ( 2 * singleLineHeight ) > ( height() - 2 * margin ) ) { int x = margin, txtWidth; if ( mIncidence->allDay() ) { x += visRect.left(); const int y = qRound( ( height() - 16 ) / 2.0 ); paintIcons( &p, x, y, ft ); txtWidth = visRect.right() - margin - x; } else { const int y = qRound( ( height() - 16 ) / 2.0 ); paintIcons( &p, x, y, ft ); txtWidth = width() - margin - x; } const int y = ( ( height() - singleLineHeight ) / 2 ) + fm.ascent(); KWordWrap::drawFadeoutText( &p, x, y, txtWidth, mLabelText ); return; } // case 3: enough for 2-5 lines, but not for the header. // Also used for the middle days in multi-events if ( ( ( !completelyRenderable ) && ( ( height() - ( 2 * margin ) ) <= ( 5 * singleLineHeight ) ) ) || ( isMultiItem() && mMultiItemInfo->mNextMultiItem && mMultiItemInfo->mFirstMultiItem ) ) { int x = margin, txtWidth; if ( mIncidence->allDay() ) { x += visRect.left(); paintIcons( &p, x, margin, ft ); txtWidth = visRect.right() - margin - x; } else { paintIcons( &p, x, margin, ft ); txtWidth = width() - margin - x; } ww = KWordWrap::formatText( fm, QRect( 0, 0, txtWidth, ( height() - ( 2 * margin ) ) ), 0, mLabelText ); ww->drawText( &p, x, margin, Qt::AlignHCenter | KWordWrap::FadeOut ); delete ww; return; } // case 4: paint everything, with header: // consists of (vertically) ft + headline&icons + ft + text + margin int y = 2 * ft + hlHeight; if ( completelyRenderable ) { y += ( height() - ( 2 * ft ) - margin - hlHeight - th ) / 2; } int x = margin, txtWidth, hTxtWidth, eventX; if ( mIncidence->allDay() ) { shortH.clear(); longH.clear(); if ( const KCalCore::Event::Ptr event = CalendarSupport::event( mIncidence ) ) { if ( event->isMultiDay( mEventView->preferences()->timeSpec() ) ) { // multi-day, all-day event shortH = i18n( "%1 - %2", KGlobal::locale()->formatDate( mIncidence->dtStart().toTimeSpec( mEventView->preferences()->timeSpec() ).date() ), KGlobal::locale()->formatDate( mIncidence->dateTime( KCalCore::Incidence::RoleEnd ).toTimeSpec( mEventView->preferences()->timeSpec() ).date() ) ); longH = shortH; // paint headline drawRoundedRect( &p, QRect( fmargin, fmargin, width() - fmargin * 2, - fmargin * 2 + margin + hlHeight ), mSelected, frameColor, false, ft, roundTop, false ); } else { // single-day, all-day event // paint headline drawRoundedRect( &p, QRect( fmargin, fmargin, width() - fmargin * 2, - fmargin * 2 + margin + hlHeight ), mSelected, frameColor, false, ft, roundTop, false ); } } else { // to-do // paint headline drawRoundedRect( &p, QRect( fmargin, fmargin, width() - fmargin * 2, - fmargin * 2 + margin + hlHeight ), mSelected, frameColor, false, ft, roundTop, false ); } x += visRect.left(); eventX = x; txtWidth = visRect.right() - margin - x; paintIcons( &p, x, margin / 2, ft ); hTxtWidth = visRect.right() - margin - x; } else { // paint headline drawRoundedRect( &p, QRect( fmargin, fmargin, width() - fmargin * 2, - fmargin * 2 + margin + hlHeight ), mSelected, frameColor, false, ft, roundTop, false ); txtWidth = width() - margin - x; eventX = x; paintIcons( &p, x, margin / 2, ft ); hTxtWidth = width() - margin - x; } QString headline; int hw = fm.boundingRect( longH ).width(); if ( hw > hTxtWidth ) { headline = shortH; hw = fm.boundingRect( shortH ).width(); if ( hw < txtWidth ) { x += ( hTxtWidth - hw ) / 2; } } else { headline = longH; x += ( hTxtWidth - hw ) / 2; } p.setBackground( QBrush( frameColor ) ); p.setPen( EventViews::getTextColor( frameColor ) ); KWordWrap::drawFadeoutText( &p, x, ( margin + hlHeight + fm.ascent() ) / 2 - 2, hTxtWidth, headline ); // draw event text ww = KWordWrap::formatText( fm, QRect( 0, 0, txtWidth, height() - margin - y ), 0, mLabelText ); p.setBackground( QBrush( bgColor ) ); p.setPen( textColor ); QString ws = ww->wrappedString(); if ( ws.left( ws.length()-1 ).indexOf( QLatin1Char('\n') ) >= 0 ) { ww->drawText( &p, eventX, y, Qt::AlignLeft | KWordWrap::FadeOut ); } else { ww->drawText( &p, eventX + ( txtWidth - ww->boundingRect().width() - 2 * margin ) / 2, y, Qt::AlignHCenter | KWordWrap::FadeOut ); } delete ww; } void AgendaItem::drawRoundedRect( QPainter *p, const QRect &rect, bool selected, const QColor &bgColor, bool frame, int ft, bool roundTop, bool roundBottom ) { Q_UNUSED( ft ); if ( !mValid ) { return; } QRect r = rect; r.adjust( 0, 0, 1, 1 ); p->save(); QPainterPath path; bool shrinkWidth = r.width() < 16; bool shrinkHeight = r.height() < 16; qreal rnd = 2.1; int sw = shrinkWidth ? 10 : 11; int sh = shrinkHeight ? 10 : 11; QRectF tr( r.x() + r.width() - sw - rnd, r.y() + rnd, sw, sh ); QRectF tl( r.x() + rnd, r.y() + rnd, sw, sh ); QRectF bl( r.x() + rnd, r.y() + r.height() - sh - 1 - rnd, sw, sh ); QRectF br( r.x() + r.width() - sw - rnd, r.y() + r.height() - sh - 1 - rnd, sw, sh ); if( roundTop ) { path.moveTo( tr.topRight() ); path.arcTo( tr, 0.0, 90.0 ); path.lineTo( tl.topRight() ); path.arcTo( tl, 90.0, 90.0 ); } else { path.moveTo( tr.topRight() ); path.lineTo( tl.topLeft() ); } if( roundBottom ) { path.lineTo( bl.topLeft() ); path.arcTo( bl, 180.0, 90.0 ); path.lineTo( br.bottomLeft() ); path.arcTo( br, 270.0, 90.0 ); } else { path.lineTo( bl.bottomLeft() ); path.lineTo( br.bottomRight() ); } path.closeSubpath(); // header if ( !frame ) { QLinearGradient gradient( QPointF( r.x(), r.y() ), QPointF( r.x(), r.height() ) ); if ( selected ) { QColor top = bgColor.dark( 250 ); top.setAlpha( 40 ); gradient.setColorAt( 0, top ); gradient.setColorAt( 1, QColor( 255, 255, 255, 30 ) ); } else { gradient.setColorAt( 0, QColor( 255, 255, 255, 90 ) ); gradient.setColorAt( 1, QColor( 0, 0, 0, 10 ) ); } p->setBrush( bgColor ); p->setPen( Qt::NoPen ); p->drawPath( path ); p->setBrush( gradient ); p->setPen( Qt::NoPen ); p->drawPath( path ); QPixmap separator; QString key( QLatin1String("ko_hsep") ); if ( !QPixmapCache::find( key, separator ) ) { separator = QPixmap( QLatin1String(":/headerSeparator.png") ); QPixmapCache::insert( key, separator ); } p->fillRect( QRect( r.x() + 3, r.y() + r.height() - 2, r.x() + r.width() - 4, 2 ), QBrush( separator ) ); p->restore(); return; } QLinearGradient gradient( QPointF( r.x(), r.y() ), QPointF( r.x(), r.height() ) ); if ( r.height() > 50 ) { if ( mIncidence->allDay() && mIncidence->dtStart() == mIncidence->dateTime( KCalCore::Incidence::RoleEnd ) && CalendarSupport::hasEvent( mIncidence ) ) { gradient.setColorAt( 0, bgColor.light( 130 ) ); qreal t = 1.0 - ( r.height() - 18.0 ) / r.height(); gradient.setColorAt( t, bgColor.light( 115 ) ); qreal b = ( r.height() - 20.0 ) / r.height(); gradient.setColorAt( b, bgColor ); } else { gradient.setColorAt( 0, bgColor.light( 115 ) ); qreal b = ( r.height() - 20.0 ) / r.height(); gradient.setColorAt( b, bgColor ); } gradient.setColorAt( 1, bgColor.dark( 110 ) ); } else { if ( mIncidence->allDay() && mIncidence->dtStart() == mIncidence->dateTime( KCalCore::Incidence::RoleEnd ) && !CalendarSupport::hasTodo( mIncidence ) ) { gradient.setColorAt( 0, bgColor.light( 130 ) ); gradient.setColorAt( 0.35, bgColor.light( 115 ) ); gradient.setColorAt( 0.65, bgColor ); } else { gradient.setColorAt( 0, bgColor.light( 115 ) ); gradient.setColorAt( 0.65, bgColor ); } gradient.setColorAt( 1, bgColor.dark( 110 ) ); } p->setBrush( gradient ); p->setPen( Qt::NoPen ); p->drawPath( path ); p->setRenderHint( QPainter::Antialiasing, false ); if ( r.width() - 16 > 0 ) { QPixmap topLines; QString key( QLatin1String("ko_t") ); if ( !QPixmapCache::find( key, topLines ) ) { topLines = QPixmap( QLatin1String(":/topLines.png") ); QPixmapCache::insert( key, topLines ); } p->setBrushOrigin( r.x() + 8, r.y() ); p->fillRect( QRect( r.x() + 8, r.y(), r.width() - 16, 5 ), QBrush( topLines ) ); QPixmap bottomLines; key = QLatin1String("ko_b"); if ( !QPixmapCache::find( key, bottomLines ) ) { bottomLines = QPixmap( QLatin1String(":/bottomLines.png") ); QPixmapCache::insert( key, bottomLines ); } p->setBrushOrigin( r.x() + 8, r.y() + r.height() - 6 ); p->fillRect( QRect( r.x() + 8, r.y() + r.height() - 6, r.width() - 16, 6 ), QBrush( bottomLines ) ); } if ( r.height() - 16 > 0 ) { QPixmap leftLines; QString key( QLatin1String("ko_l") ); if ( !QPixmapCache::find( key, leftLines ) ) { leftLines = QPixmap( QLatin1String(":/leftLines.png") ); QPixmapCache::insert( key, leftLines ); } p->setBrushOrigin( r.x(), r.y() + 8 ); p->fillRect( QRect( r.x(), r.y() + 8, 5, r.height() - 16 ), QBrush( leftLines ) ); QPixmap rightLines; key = QLatin1String( "ko_r" ); if ( !QPixmapCache::find( key, rightLines ) ) { rightLines = QPixmap( QLatin1String(":/rightLines.png") ); QPixmapCache::insert( key, rightLines ); } p->setBrushOrigin( r.x() + r.width() - 5, r.y() + 8 ); p->fillRect( QRect( r.x() + r.width() - 5, r.y() + 8, 5, r.height() - 16 ), QBrush( rightLines ) ); } // don't overlap the edges int lw = shrinkWidth ? r.width() / 2 : 8; int rw = shrinkWidth ? r.width() - lw : 8; int th = shrinkHeight ? r.height() / 2 : 8; int bh = shrinkHeight ? r.height() - th : 8; // keep the bottom round for items which ending at 00:15 if( shrinkHeight && !roundTop && roundBottom && r.height() > 3 ) { bh += th - 3; th = 3; } QPixmap topLeft; QString key = roundTop ? QLatin1String( "ko_tl" ) : QLatin1String( "ko_rtl" ); if ( !QPixmapCache::find( key, topLeft ) ) { topLeft = roundTop ? QPixmap( QLatin1String(":/roundTopLeft.png") ) : QPixmap( QLatin1String(":/rectangularTopLeft.png") ); QPixmapCache::insert( key, topLeft ); } p->drawPixmap( r.x(), r.y(), topLeft, 0, 0, lw, th ); QPixmap topRight; key = roundTop ? QLatin1String( "ko_tr" ) : QLatin1String( "ko_rtr" ); if ( !QPixmapCache::find( key, topRight ) ) { topRight = roundTop ? QPixmap( QLatin1String(":/roundTopRight.png") ) : QPixmap( QLatin1String(":/rectangularTopRight.png") ); QPixmapCache::insert( key, topRight ); } p->drawPixmap( r.x() + r.width() - rw, r.y(), topRight, 8 - rw, 0, rw, th ); QPixmap bottomLeft; key = roundBottom ? QLatin1String( "ko_bl" ) : QLatin1String( "ko_rbl" ); if ( !QPixmapCache::find( key, bottomLeft ) ) { bottomLeft = roundBottom ? QPixmap( QLatin1String(":/roundBottomLeft.png") ) : QPixmap( QLatin1String(":/rectangularBottomLeft.png") ); QPixmapCache::insert( key, bottomLeft ); } p->drawPixmap( r.x(), r.y() + r.height() - bh, bottomLeft, 0, 8 - bh, lw, bh ); QPixmap bottomRight; key = roundBottom ? QLatin1String( "ko_br" ) : QLatin1String( "ko_rbr" ); if ( !QPixmapCache::find( key, bottomRight ) ) { bottomRight = roundBottom ? QPixmap( QLatin1String(":/roundBottomRight.png") ) : QPixmap( QLatin1String(":/rectangularBottomRight.png") ); QPixmapCache::insert( key, bottomRight ); } p->drawPixmap( r.x() + r.width() - rw, r.y() + r.height() - bh, bottomRight, 8 - rw, 8 - bh, rw, 8 ); p->restore(); } bool AgendaItem::eventFilter( QObject *obj, QEvent *event ) { if ( event->type() == QEvent::Paint ) { return mValid; } else { // standard event processing return QObject::eventFilter( obj, event ); } } bool AgendaItem::event( QEvent *event ) { if ( event->type() == QEvent::ToolTip ) { if( !mEventView->preferences()->enableToolTips() ) { return true; } else if ( mValid ) { QHelpEvent *helpEvent = static_cast( event ); QToolTip::showText( helpEvent->globalPos(), KCalUtils::IncidenceFormatter::toolTipStr( mCalendar->displayName(mIncidence), mIncidence, occurrenceDate(), true, mEventView->preferences()->timeSpec() ), this ); } } return QWidget::event( event ); } diff --git a/calendarviews/agenda/agendaview.cpp b/calendarviews/agenda/agendaview.cpp index b5b7e5bfc3..e50ccabd81 100644 --- a/calendarviews/agenda/agendaview.cpp +++ b/calendarviews/agenda/agendaview.cpp @@ -1,2320 +1,2323 @@ /* Copyright (c) 2001 Cornelius Schumacher Copyright (C) 2003-2004 Reinhold Kainhofer Copyright (C) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net Author: Kevin Krammer, krake@kdab.com Author: Sergio Martins, sergio.martins@kdab.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #include "agendaview.h" #include "agenda.h" #include "agendaitem.h" #include "viewcalendar.h" #include "alternatelabel.h" #include "calendardecoration.h" #include "decorationlabel.h" #include "prefs.h" #include "timelabels.h" #include "timelabelszone.h" #include #include #include #include #include #include #include #include #include #include // for SmallIcon() #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace EventViews; enum { SPACING = 2 }; enum { SHRINKDOWN = 2 // points less for the timezone font }; class EventIndicator::Private { EventIndicator *const q; public: Private( EventIndicator *parent, EventIndicator::Location loc ) : q( parent ), mColumns( 1 ), mLocation( loc ) { mEnabled.resize( mColumns ); QChar ch; // Dashed up and down arrow characters ch = QChar( mLocation == Top ? 0x21e1 : 0x21e3 ); QFont font = q->font(); font.setPixelSize( KIconLoader::global()->currentSize( KIconLoader::Dialog ) ); QFontMetrics fm( font ); QRect rect = fm.boundingRect( ch ).adjusted( -2, -2, 2, 2 ); mPixmap = QPixmap( rect.size() ); mPixmap.fill( Qt::transparent ); QPainter p( &mPixmap ); p.setOpacity( 0.33 ); p.setFont( font ); p.setPen( q->palette().text().color() ); p.drawText( -rect.left(), -rect.top(), ch ); } void adjustGeometry() { QRect rect; rect.setWidth( q->parentWidget()->width() ); rect.setHeight( q->height() ); rect.setLeft( 0 ); rect.setTop( mLocation == EventIndicator::Top ? 0 : q->parentWidget()->height() - rect.height() ); q->setGeometry( rect ); } public: int mColumns; Location mLocation; QPixmap mPixmap; QVector mEnabled; }; EventIndicator::EventIndicator( Location loc, QWidget *parent ) : QFrame( parent ), d( new Private( this, loc ) ) { setAttribute( Qt::WA_TransparentForMouseEvents ); setFixedHeight( d->mPixmap.height() ); parent->installEventFilter( this ); } EventIndicator::~EventIndicator() { delete d; } void EventIndicator::paintEvent( QPaintEvent * ) { QPainter painter( this ); const double cellWidth = static_cast( width() ) / d->mColumns; const bool isRightToLeft = QApplication::isRightToLeft(); const uint pixmapOffset = isRightToLeft ? 0 : ( cellWidth - d->mPixmap.width() ); for ( int i = 0; i < d->mColumns; ++i ) { if ( d->mEnabled[ i ] ) { const int xOffset = ( isRightToLeft ? ( d->mColumns - 1 - i ) : i ) * cellWidth; painter.drawPixmap( xOffset + pixmapOffset, 0, d->mPixmap ); } } } bool EventIndicator::eventFilter( QObject *, QEvent * event ) { if ( event->type() == QEvent::Resize ) { d->adjustGeometry(); } return false; } void EventIndicator::changeColumns( int columns ) { d->mColumns = columns; d->mEnabled.resize( d->mColumns ); show(); raise(); update(); } void EventIndicator::enableColumn( int column, bool enable ) { Q_ASSERT( column < d->mEnabled.count() ); d->mEnabled[ column ] = enable; } //////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////// class AgendaView::Private : public Akonadi::ETMCalendar::CalendarObserver { AgendaView *const q; public: explicit Private( AgendaView *parent, bool isInteractive, bool isSideBySide ) : q( parent ), mTopDayLabels( 0 ), mLayoutTopDayLabels( 0 ), mTopDayLabelsFrame( 0 ), mLayoutBottomDayLabels( 0 ), mBottomDayLabels( 0 ), mBottomDayLabelsFrame( 0 ), mTimeBarHeaderFrame( 0 ), mAllDayAgenda( 0 ), mAgenda( 0 ), mTimeLabelsZone( 0 ), mAllowAgendaUpdate( true ), mUpdateItem( 0 ), mIsSideBySide( isSideBySide ), mDummyAllDayLeft( 0 ), mUpdateAllDayAgenda( true ), mUpdateAgenda( true ), mIsInteractive( isInteractive ), - mUpdateEventIndicatorsScheduled( false ), - mViewCalendar(MultiViewCalendar::Ptr( new MultiViewCalendar())) + mUpdateEventIndicatorsScheduled( false ) { - mViewCalendar->mAgendaView = q; - mViewCalendar->setETMCalendar(q->calendar()); } public: // view widgets QGridLayout *mGridLayout; QFrame *mTopDayLabels; QBoxLayout *mLayoutTopDayLabels; KHBox *mTopDayLabelsFrame; QList mDateDayLabels; QBoxLayout *mLayoutBottomDayLabels; QFrame *mBottomDayLabels; KHBox *mBottomDayLabelsFrame; KHBox *mAllDayFrame; QWidget *mTimeBarHeaderFrame; QSplitter *mSplitterAgenda; QList mTimeBarHeaders; Agenda *mAllDayAgenda; Agenda *mAgenda; TimeLabelsZone *mTimeLabelsZone; KCalCore::DateList mSelectedDates; // List of dates to be displayed KCalCore::DateList mSaveSelectedDates; // Save the list of dates between updateViews int mViewType; EventIndicator *mEventIndicatorTop; EventIndicator *mEventIndicatorBottom; QVector mMinY; QVector mMaxY; QVector mHolidayMask; QDateTime mTimeSpanBegin; QDateTime mTimeSpanEnd; bool mTimeSpanInAllDay; bool mAllowAgendaUpdate; Akonadi::Item mUpdateItem; const bool mIsSideBySide; QWidget *mDummyAllDayLeft; bool mUpdateAllDayAgenda; bool mUpdateAgenda; bool mIsInteractive; bool mUpdateEventIndicatorsScheduled; // Contains days that have at least one all-day Event with TRANSP: OPAQUE ( busy ) // that has you as organizer or attendee so we can color background with a different // color QMap mBusyDays; - EventViews::MultiViewCalendar::Ptr mViewCalendar; bool makesWholeDayBusy( const KCalCore::Incidence::Ptr &incidence ) const; CalendarDecoration::Decoration *loadCalendarDecoration( const QString &name ); void clearView(); void setChanges( EventView::Changes changes, const KCalCore::Incidence::Ptr &incidence = KCalCore::Incidence::Ptr() ); /** Returns a list of consecutive dates, starting with @p start and ending with @p end. If either start or end are invalid, a list with QDate::currentDate() is returned */ static QList generateDateList( const QDate &start, const QDate &end ); void changeColumns( int numColumns ); - AgendaItem::List agendaItems( const QString &uid ) const; + AgendaItem::List agendaItems( const KCalCore::Incidence::Ptr &incidence ) const; // insertAtDateTime is in the view's timezone void insertIncidence( const KCalCore::Incidence::Ptr &, const KDateTime &recurrenceId, const KDateTime &insertAtDateTime, bool createSelected ); void reevaluateIncidence( const KCalCore::Incidence::Ptr &incidence ); bool datesEqual( const KCalCore::Incidence::Ptr &one, const KCalCore::Incidence::Ptr &two ) const; /** * Returns false if the incidence is for sure outside of the visible timespan. * Returns true if it might be, meaning that to be sure, timezones must be * taken into account. * This is a very fast way of discarding incidences that are outside of the * timespan and only performing expensive timezone operations on the ones * that might be viisble */ bool mightBeVisible( const KCalCore::Incidence::Ptr &incidence ) const; protected: /* reimplemented from KCalCore::Calendar::CalendarObserver */ void calendarIncidenceAdded( const KCalCore::Incidence::Ptr &incidence ); void calendarIncidenceChanged( const KCalCore::Incidence::Ptr &incidence ); void calendarIncidenceDeleted( const KCalCore::Incidence::Ptr &incidence ); }; bool AgendaView::Private::datesEqual( const KCalCore::Incidence::Ptr &one, const KCalCore::Incidence::Ptr &two ) const { const KDateTime start1 = one->dtStart(); const KDateTime start2 = two->dtStart(); const KDateTime end1 = one->dateTime( KCalCore::Incidence::RoleDisplayEnd ); const KDateTime end2 = two->dateTime( KCalCore::Incidence::RoleDisplayEnd ); if ( start1.isValid() ^ start2.isValid() ) return false; if ( end1.isValid() ^ end2.isValid() ) return false; if ( start1.isValid() && start1 != start2 ) return false; if ( end1.isValid() && end1 != end2 ) return false; return true; } -AgendaItem::List AgendaView::Private::agendaItems( const QString &uid ) const +AgendaItem::List AgendaView::Private::agendaItems( const KCalCore::Incidence::Ptr &incidence ) const { - AgendaItem::List allDayAgendaItems = mAllDayAgenda->agendaItems( uid ); - return allDayAgendaItems.isEmpty() ? mAgenda->agendaItems( uid ) : allDayAgendaItems; + AgendaItem::List allDayAgendaItems = mAllDayAgenda->agendaItems( incidence ); + return allDayAgendaItems.isEmpty() ? mAgenda->agendaItems( incidence ) : allDayAgendaItems; } bool AgendaView::Private::mightBeVisible( const KCalCore::Incidence::Ptr &incidence ) const { KCalCore::Todo::Ptr todo = incidence.dynamicCast(); const KDateTime::Spec timeSpec = q->preferences()->timeSpec(); KDateTime firstVisibleDateTime( mSelectedDates.first(), timeSpec ); KDateTime lastVisibleDateTime( mSelectedDates.last(), timeSpec ); // KDateTime::toTimeSpec() is expensive, so lets first compare only the date, // to see if the incidence is visible. // If it's more than 48h of diff, then for sure it won't be visible, // independently of timezone. // The largest difference between two timezones is about 24 hours. if ( todo && todo->isOverdue() ) { // Don't optimize this case. Overdue to-dos have their own rules for displaying themselves return true; } if ( !incidence->recurs() ) { // If DTEND/DTDUE is before the 1st visible column if ( incidence->dateTime( KCalCore::Incidence::RoleEnd ).date().daysTo( firstVisibleDateTime.date() ) > 2 ) { return false; } // if DTSTART is after the last visible column if ( !todo && lastVisibleDateTime.date().daysTo( incidence->dtStart().date() ) > 2 ) { return false; } // if DTDUE is after the last visible column if ( todo && lastVisibleDateTime.date().daysTo( todo->dtDue().date() ) > 2 ) { return false; } } return true; } void AgendaView::Private::changeColumns( int numColumns ) { // mMinY, mMaxY and mEnabled must all have the same size. // Make sure you preserve this order because mEventIndicatorTop->changeColumns() // can trigger a lot of stuff, and code will be executed when mMinY wasn't resized yet. mMinY.resize( numColumns ); mMaxY.resize( numColumns ); mEventIndicatorTop->changeColumns( numColumns ); mEventIndicatorBottom->changeColumns( numColumns ); } /** static */ QList AgendaView::Private::generateDateList( const QDate &start, const QDate &end ) { QList list; if ( start.isValid() && end.isValid() && end >= start && start.daysTo( end ) < AgendaView::MAX_DAY_COUNT ) { QDate date = start; while ( date <= end ) { list.append( date ); date = date.addDays( 1 ); } } else { list.append( QDate::currentDate() ); } return list; } void AgendaView::Private::reevaluateIncidence( const KCalCore::Incidence::Ptr &incidence ) { - if (!incidence || !mViewCalendar->isValid( incidence )) { + if (!incidence || !q->viewCalendar()->isValid( incidence )) { kWarning() << "invalid or unknown incidence." << incidence; return; } q->removeIncidence( incidence ); q->displayIncidence( incidence, false ); mAgenda->checkScrollBoundaries(); q->updateEventIndicators(); } void AgendaView::Private::calendarIncidenceAdded( const KCalCore::Incidence::Ptr &incidence ) { - if (!incidence || !mViewCalendar->isValid( incidence )) { + if (!incidence || !q->viewCalendar()->isValid( incidence )) { kWarning() << "invalid or unknown incidence." << incidence; Q_ASSERT( false ); return; } - if ( incidence->hasRecurrenceId() && mViewCalendar->isValid(incidence)) { + if ( incidence->hasRecurrenceId() && q->viewCalendar()->isValid(incidence)) { // Reevaluate the main event instead, if it was inserted before this one - KCalCore::Incidence::Ptr mainIncidence = q->calendar2(incidence)->incidence( incidence->uid() ); + QSharedPointer cal = q->calendar(incidence).staticCast(); + KCalCore::Incidence::Ptr mainIncidence = cal->incidence(cal->calendar(incidence), incidence->uid()); if ( mainIncidence ) { reevaluateIncidence( mainIncidence ); + } else { + kWarning() << "Failed to find main incidence for " << incidence->instanceIdentifier(); } } else if ( q->displayIncidence( incidence, false ) ) { mAgenda->checkScrollBoundaries(); q->scheduleUpdateEventIndicators(); } } void AgendaView::Private::calendarIncidenceChanged( const KCalCore::Incidence::Ptr &incidence ) { if ( !incidence || incidence->uid().isEmpty() ) { kError() << "AgendaView::calendarIncidenceChanged() Invalid incidence or empty UID. " << incidence; Q_ASSERT( false ); return; } - AgendaItem::List agendaItems = this->agendaItems( incidence->uid() ); + AgendaItem::List agendaItems = this->agendaItems( incidence ); if ( agendaItems.isEmpty() ) { - kWarning() << "AgendaView::calendarIncidenceChanged() Invalid agendaItem for incidence " << incidence->uid(); + kWarning() << "AgendaView::calendarIncidenceChanged() Invalid agendaItem for incidence " << q->viewCalendar()->uid(incidence); return; } - // Optimization: If the dates didn't change, just repaint it. - // This optimization for now because we need to process collisions between agenda items. - if ( false && !incidence->recurs() && agendaItems.count() == 1 ) { - KCalCore::Incidence::Ptr originalIncidence = agendaItems.first()->incidence(); - - if ( datesEqual( originalIncidence, incidence ) ) { - foreach ( const AgendaItem::QPtr &agendaItem, agendaItems ) { - agendaItem->setIncidence( KCalCore::Incidence::Ptr( incidence->clone() ) ); - agendaItem->update(); - } - return; - } - } - - if ( incidence->hasRecurrenceId() && mViewCalendar->isValid(incidence) ) { + if ( incidence->hasRecurrenceId() && q->viewCalendar()->isValid(incidence) ) { // Reevaluate the main event instead, if it exists - KCalCore::Incidence::Ptr mainIncidence = q->calendar2(incidence)->incidence( incidence->uid() ); + QSharedPointer cal = q->calendar(incidence).staticCast(); + KCalCore::Incidence::Ptr mainIncidence = cal->incidence(cal->calendar(incidence), incidence->uid()); reevaluateIncidence( mainIncidence ? mainIncidence : incidence ); } else { reevaluateIncidence( incidence ); } // No need to call setChanges(), that triggers a fillAgenda() // setChanges( q->changes() | IncidencesEdited, incidence ); } void AgendaView::Private::calendarIncidenceDeleted( const KCalCore::Incidence::Ptr &incidence ) { if ( !incidence || incidence->uid().isEmpty() ) { kWarning() << "invalid incidence or empty uid: " << incidence; Q_ASSERT( false ); return; } q->removeIncidence( incidence ); if ( incidence->hasRecurrenceId()) { // Reevaluate the main event, if it exists. The exception was removed so the main recurrent series // will no be bigger. - if ( mViewCalendar->isValid(incidence->uid()) ) { - KCalCore::Incidence::Ptr mainIncidence = q->calendar2(incidence->uid())->incidence( incidence->uid() ); + if ( q->viewCalendar()->isValid(incidence) ) { + QSharedPointer cal = q->calendar(incidence).staticCast(); + KCalCore::Incidence::Ptr mainIncidence = cal->incidence(cal->calendar(incidence), incidence->uid()); if ( mainIncidence ) { reevaluateIncidence( mainIncidence ); + } else { + kWarning() << "Failed to find main incidence for " << incidence->instanceIdentifier(); } } } else if ( mightBeVisible( incidence ) ) { // No need to call setChanges(), that triggers a fillAgenda() // setChanges( q->changes() | IncidencesDeleted, CalendarSupport::incidence( incidence ) ); mAgenda->checkScrollBoundaries(); q->scheduleUpdateEventIndicators(); } } void EventViews::AgendaView::Private::setChanges( EventView::Changes changes, const KCalCore::Incidence::Ptr &incidence ) { // We could just call EventView::setChanges(...) but we're going to do a little // optimization. If only an all day item was changed, only all day agenda // should be updated. // all bits = 1 const int ones = ~0; const int incidenceOperations = IncidencesAdded | IncidencesEdited | IncidencesDeleted; // If changes has a flag turned on, other than incidence operations, then update both agendas if ( ( ones ^ incidenceOperations ) & changes ) { mUpdateAllDayAgenda = true; mUpdateAgenda = true; } else if ( incidence ) { mUpdateAllDayAgenda = mUpdateAllDayAgenda | incidence->allDay(); mUpdateAgenda = mUpdateAgenda | !incidence->allDay(); } q->EventView::setChanges( changes ); } void AgendaView::Private::clearView() { if ( mUpdateAllDayAgenda ) { mAllDayAgenda->clear(); } if ( mUpdateAgenda ) { mAgenda->clear(); } mBusyDays.clear(); } void AgendaView::Private::insertIncidence( const KCalCore::Incidence::Ptr &incidence, const KDateTime &recurrenceId, const KDateTime &insertAtDateTime, bool createSelected ) { if ( !q->filterByCollectionSelection( incidence ) ) { return; } KCalCore::Event::Ptr event = CalendarSupport::event( incidence ); KCalCore::Todo::Ptr todo = CalendarSupport::todo( incidence ); const QDate insertAtDate = insertAtDateTime.date(); // In case incidence->dtStart() isn't visible (crosses bounderies) const int curCol = qMax( mSelectedDates.first().daysTo( insertAtDate ), 0 ); // The date for the event is not displayed, just ignore it if ( curCol >= mSelectedDates.count() ) { return; } if ( mMinY.count() <= curCol ) { mMinY.resize( mSelectedDates.count() ); } if ( mMaxY.count() <= curCol ) { mMaxY.resize( mSelectedDates.count() ); } // Default values, which can never be reached mMinY[curCol] = mAgenda->timeToY( QTime( 23, 59 ) ) + 1; mMaxY[curCol] = mAgenda->timeToY( QTime( 0, 0 ) ) - 1; int beginX; int endX; if ( event ) { const QDate firstVisibleDate = mSelectedDates.first(); // its crossing bounderies, lets calculate beginX and endX const int duration = event->dtStart().toTimeSpec( q->preferences()->timeSpec() ).daysTo( event->dtEnd() ); if ( insertAtDate < firstVisibleDate ) { beginX = curCol + firstVisibleDate.daysTo( insertAtDate ); endX = beginX + duration; } else { beginX = curCol; endX = beginX + duration; } } else if ( todo ) { if ( !todo->hasDueDate() ) { return; // todo shall not be displayed if it has no date } beginX = endX = curCol; } else { return; } const KDateTime::Spec timeSpec = q->preferences()->timeSpec(); const QDate today = KDateTime::currentDateTime( timeSpec ).date(); if ( todo && todo->isOverdue() && today >= insertAtDate ) { mAllDayAgenda->insertAllDayItem( incidence, recurrenceId, curCol, curCol, createSelected ); } else if ( incidence->allDay() ) { mAllDayAgenda->insertAllDayItem( incidence, recurrenceId, beginX, endX, createSelected ); } else if ( event && event->isMultiDay( timeSpec ) ) { // TODO: We need a better isMultiDay(), one that receives the occurrence. // In the single-day handling code there's a neat comment on why // we're calculating the start time this way const QTime startTime = insertAtDateTime.time(); // In the single-day handling code there's a neat comment on why we use the // duration instead of fetching the end time directly const int durationOfFirstOccurrence = event->dtStart().secsTo( event->dtEnd() ); QTime endTime = startTime.addSecs( durationOfFirstOccurrence ); const int startY = mAgenda->timeToY( startTime ); if ( endTime == QTime( 0, 0, 0 ) ) { endTime = QTime( 23, 59, 59 ); } const int endY = mAgenda->timeToY( endTime ) - 1; if ( ( beginX <= 0 && curCol == 0 ) || beginX == curCol ) { mAgenda->insertMultiItem( incidence, recurrenceId, beginX, endX, startY, endY, createSelected ); } if ( beginX == curCol ) { mMaxY[curCol] = mAgenda->timeToY( QTime( 23, 59 ) ); if ( startY < mMinY[curCol] ) { mMinY[curCol] = startY; } } else if ( endX == curCol ) { mMinY[curCol] = mAgenda->timeToY( QTime( 0, 0 ) ); if ( endY > mMaxY[curCol] ) { mMaxY[curCol] = endY; } } else { mMinY[curCol] = mAgenda->timeToY( QTime( 0, 0 ) ); mMaxY[curCol] = mAgenda->timeToY( QTime( 23, 59 ) ); } } else { int startY = 0, endY = 0; if ( event ) { // Single day events fall here // Don't use event->dtStart().toTimeSpec( timeSpec ).time(). // If it's a UTC recurring event it should have a different time when it crosses DST, // so we must use insertAtDate here, so we get the correct time. // // The nth occurrence doesn't always have the same time as the 1st occurrence. const QTime startTime = insertAtDateTime.time(); // We could just fetch the end time directly from dtEnd() instead of adding a duration to the // start time. This way is best because it preserves the duration of the event. There are some // corner cases where the duration would be messed up, for example a UTC event that when // converted to local has dtStart() in day light saving time, but dtEnd() outside DST. // It could create events with 0 duration. const int durationOfFirstOccurrence = event->dtStart().secsTo( event->dtEnd() ); QTime endTime = startTime.addSecs( durationOfFirstOccurrence ); startY = mAgenda->timeToY( startTime ); if ( endTime == QTime( 0, 0, 0 ) ) { endTime = QTime( 23, 59, 59 ); } endY = mAgenda->timeToY( endTime ) - 1; } if ( todo ) { QTime t; if ( todo->recurs() ) { // The time we get depends on the insertAtDate, because of daylight savings changes const KDateTime ocurrrenceDateTime = KDateTime( insertAtDate, todo->dtDue().time(), todo->dtDue().timeSpec() ); t = ocurrrenceDateTime.toTimeSpec( timeSpec ).time(); } else { t = todo->dtDue().toTimeSpec( timeSpec ).time(); } if ( t == QTime( 0, 0 ) && !todo->recurs() ) { // To-dos due at 00h00 are drawn at the previous day and ending at // 23h59. For recurring to-dos, that's not being done because it wasn't // implemented yet in ::fillAgenda(). t = QTime( 23, 59 ); } const int halfHour = 1800; if ( t.addSecs( -halfHour ) < t ) { startY = mAgenda->timeToY( t.addSecs( -halfHour ) ); endY = mAgenda->timeToY( t ) - 1; } else { startY = 0; endY = mAgenda->timeToY( t.addSecs( halfHour ) ) - 1; } } if ( endY < startY ) { endY = startY; } mAgenda->insertItem( incidence, recurrenceId, curCol, startY, endY, 1, 1, createSelected ); if ( startY < mMinY[curCol] ) { mMinY[curCol] = startY; } if ( endY > mMaxY[curCol] ) { mMaxY[curCol] = endY; } } } //////////////////////////////////////////////////////////////////////////// AgendaView::AgendaView( const QDate &start, const QDate &end, bool isInteractive, bool isSideBySide, QWidget *parent ) : EventView( parent ), d( new Private( this, isInteractive, isSideBySide ) ) { init( start, end ); } AgendaView::AgendaView( const PrefsPtr &prefs, const QDate &start, const QDate &end, bool isInteractive, bool isSideBySide, QWidget *parent ) : EventView( parent ), d( new Private( this, isInteractive, isSideBySide ) ) { setPreferences( prefs ); init( start, end ); } void AgendaView::init( const QDate &start, const QDate &end ) { d->mSelectedDates = Private::generateDateList( start, end ); d->mGridLayout = new QGridLayout( this ); d->mGridLayout->setMargin( 0 ); /* Create agenda splitter */ d->mSplitterAgenda = new QSplitter( Qt::Vertical, this ); d->mGridLayout->addWidget( d->mSplitterAgenda, 1, 0 ); d->mSplitterAgenda->setOpaqueResize( KGlobalSettings::opaqueResize() ); /* Create day name labels for agenda columns */ d->mTopDayLabelsFrame = new KHBox( d->mSplitterAgenda ); d->mTopDayLabelsFrame->setSpacing( SPACING ); /* Create all-day agenda widget */ d->mAllDayFrame = new KHBox( d->mSplitterAgenda ); d->mAllDayFrame->setSpacing( SPACING ); // Alignment and description widgets if ( !d->mIsSideBySide ) { d->mTimeBarHeaderFrame = new KHBox( d->mAllDayFrame ); } // The widget itself d->mDummyAllDayLeft = new QWidget( d->mAllDayFrame ); AgendaScrollArea *allDayScrollArea = new AgendaScrollArea( true, this, d->mIsInteractive, d->mAllDayFrame ); d->mAllDayAgenda = allDayScrollArea->agenda(); /* Create the main agenda widget and the related widgets */ QWidget *agendaFrame = new QWidget( d->mSplitterAgenda ); QHBoxLayout *agendaLayout = new QHBoxLayout( agendaFrame ); agendaLayout->setMargin( 0 ); agendaLayout->setSpacing( SPACING ); // Create agenda AgendaScrollArea *scrollArea = new AgendaScrollArea( false, this, d->mIsInteractive, agendaFrame ); d->mAgenda = scrollArea->agenda(); // Create event indicator bars d->mEventIndicatorTop = new EventIndicator( EventIndicator::Top, scrollArea->viewport() ); d->mEventIndicatorBottom = new EventIndicator( EventIndicator::Bottom, scrollArea->viewport() ); // Create time labels d->mTimeLabelsZone = new TimeLabelsZone( this, preferences(), d->mAgenda ); // This timeLabelsZoneLayout is for adding some spacing // to align timelabels, to agenda's grid QVBoxLayout *timeLabelsZoneLayout = new QVBoxLayout(); agendaLayout->addLayout( timeLabelsZoneLayout ); agendaLayout->addWidget( scrollArea ); timeLabelsZoneLayout->addSpacing( scrollArea->frameWidth() ); timeLabelsZoneLayout->addWidget( d->mTimeLabelsZone ); timeLabelsZoneLayout->addSpacing( scrollArea->frameWidth() ); // Scrolling connect( d->mAgenda, SIGNAL(zoomView(int,QPoint,Qt::Orientation)), SLOT(zoomView(int,QPoint,Qt::Orientation)) ); // Event indicator updates connect( d->mAgenda, SIGNAL(lowerYChanged(int)), SLOT(updateEventIndicatorTop(int)) ); connect( d->mAgenda, SIGNAL(upperYChanged(int)), SLOT(updateEventIndicatorBottom(int)) ); if ( d->mIsSideBySide ) { d->mTimeLabelsZone->hide(); } /* Create a frame at the bottom which may be used by decorations */ d->mBottomDayLabelsFrame = new KHBox( d->mSplitterAgenda ); d->mBottomDayLabelsFrame->setSpacing( SPACING ); if ( !d->mIsSideBySide ) { /* Make the all-day and normal agendas line up with each other */ int margin = style()->pixelMetric( QStyle::PM_ScrollBarExtent ); if ( style()->styleHint( QStyle::SH_ScrollView_FrameOnlyAroundContents ) ) { // Needed for some styles. Oxygen needs it, Plastique does not. margin -= scrollArea->frameWidth(); } d->mAllDayFrame->layout()->addItem( new QSpacerItem( margin, 0 ) ); } updateTimeBarWidth(); // Don't call it now, bottom agenda isn't fully up yet QMetaObject::invokeMethod( this, "alignAgendas", Qt::QueuedConnection ); // Whoever changes this code, remember to leave createDayLabels() // inside the ctor, so it's always called before readSettings(), so // readSettings() works on the splitter that has the right amount of // widgets ( createDayLabels() via placeDecorationFrame() removes widgets). createDayLabels( true ); /* Connect the agendas */ connect( d->mAllDayAgenda, SIGNAL(newTimeSpanSignal(QPoint,QPoint)), SLOT(newTimeSpanSelectedAllDay(QPoint,QPoint)) ); connect( d->mAgenda, SIGNAL(newTimeSpanSignal(QPoint,QPoint)), SLOT(newTimeSpanSelected(QPoint,QPoint)) ); connectAgenda( d->mAgenda, d->mAllDayAgenda ); connectAgenda( d->mAllDayAgenda, d->mAgenda ); } AgendaView::~AgendaView() { - foreach(const ViewCalendar::Ptr &cal, d->mViewCalendar->mSubCalendars) { + foreach(const ViewCalendar::Ptr &cal, viewCalendar()->subCalendars()) { if (cal->getCalendar()) { cal->getCalendar()->unregisterObserver(d); } } delete d; } -KCalCore::Calendar::Ptr AgendaView::calendar2(KCalCore::Incidence::Ptr incidence) const -{ - return d->mViewCalendar->findCalendar(incidence)->getCalendar(); -} - -KCalCore::Calendar::Ptr AgendaView::calendar2(const QString &incidenceIdentifier) const -{ - return d->mViewCalendar->findCalendar(incidenceIdentifier)->getCalendar(); -} - void AgendaView::setCalendar( const Akonadi::ETMCalendar::Ptr &cal ) { - if ( calendar() ) { - calendar()->unregisterObserver( d ); + if ( etmCalendar() ) { + etmCalendar()->unregisterObserver( d ); } Q_ASSERT( cal ); EventView::setCalendar( cal ); - calendar()->registerObserver( d ); - d->mViewCalendar->setETMCalendar(cal); - d->mAgenda->setCalendar( d->mViewCalendar ); - d->mAllDayAgenda->setCalendar( d->mViewCalendar ); + cal->registerObserver( d ); + d->mAgenda->setCalendar( viewCalendar() ); + d->mAllDayAgenda->setCalendar( viewCalendar() ); +} + +void AgendaView::setCalendar(const MultiViewCalendar::Ptr &calendar) +{ + foreach (const ViewCalendar::Ptr &cal, viewCalendar()->subCalendars()) { + if (cal->getCalendar() != etmCalendar()) { + cal->getCalendar()->unregisterObserver( d ); + } + } + Q_ASSERT( calendar ); + EventViews::EventView::setCalendar(calendar); + foreach (const ViewCalendar::Ptr &cal, viewCalendar()->subCalendars()) { + if (cal->getCalendar() != etmCalendar()) { + cal->getCalendar()->registerObserver( d ); + } + } } void AgendaView::addCalendar(const ViewCalendar::Ptr &cal) { - d->mViewCalendar->addCalendar(cal); + viewCalendar()->addCalendar(cal); cal->getCalendar()->registerObserver(d); } void AgendaView::connectAgenda( Agenda *agenda, Agenda *otherAgenda ) { connect( agenda, SIGNAL(showNewEventPopupSignal()), SIGNAL(showNewEventPopupSignal()) ); connect( agenda, SIGNAL(showIncidencePopupSignal(KCalCore::Incidence::Ptr,QDate)), SLOT(slotShowIncidencePopup(KCalCore::Incidence::Ptr,QDate))); - agenda->setCalendar( d->mViewCalendar ); + agenda->setCalendar( viewCalendar() ); connect( agenda, SIGNAL(newEventSignal()), SIGNAL(newEventSignal()) ); connect( agenda, SIGNAL(newStartSelectSignal()), otherAgenda, SLOT(clearSelection()) ); connect( agenda, SIGNAL(newStartSelectSignal()), SIGNAL(timeSpanSelectionChanged()) ); connect( agenda, SIGNAL(editIncidenceSignal(KCalCore::Incidence::Ptr, KDateTime)), SLOT(slotEditIncidence(KCalCore::Incidence::Ptr, KDateTime)) ); connect( agenda, SIGNAL(showIncidenceSignal(KCalCore::Incidence::Ptr)), SLOT(slotShowIncidence(KCalCore::Incidence::Ptr)) ); connect( agenda, SIGNAL(deleteIncidenceSignal(KCalCore::Incidence::Ptr)), SLOT(slotDeleteIncidence(KCalCore::Incidence::Ptr)) ); // drag signals connect( agenda, SIGNAL(startDragSignal(KCalCore::Incidence::Ptr)), SLOT(startDrag(KCalCore::Incidence::Ptr)) ); // synchronize selections connect( agenda, SIGNAL(incidenceSelected(KCalCore::Incidence::Ptr,QDate)), otherAgenda, SLOT(deselectItem()) ); connect( agenda, SIGNAL(incidenceSelected(KCalCore::Incidence::Ptr,QDate)), SLOT(slotIncidenceSelected(KCalCore::Incidence::Ptr,QDate)) ); // rescheduling of todos by d'n'd connect( agenda, SIGNAL(droppedIncidences(KCalCore::Incidence::List,QPoint,bool)), SLOT(slotIncidencesDropped(KCalCore::Incidence::List,QPoint,bool)) ); connect( agenda, SIGNAL(droppedIncidences(QList,QPoint,bool)), SLOT(slotIncidencesDropped(QList,QPoint,bool)) ); } void AgendaView::slotIncidenceSelected(const KCalCore::Incidence::Ptr &incidence,QDate date) { - Akonadi::Item item = d->mViewCalendar->item(incidence); + Akonadi::Item item = viewCalendar()->item(incidence); if (item.isValid()) { emit incidenceSelected(item, date); } } void AgendaView::slotShowIncidencePopup(const KCalCore::Incidence::Ptr &incidence, QDate date) { - Akonadi::Item item = d->mViewCalendar->item(incidence); + Akonadi::Item item = viewCalendar()->item(incidence); kDebug() << "wanna see the popup for " << incidence->uid() << item.id(); if (item.isValid()) { emit showIncidencePopupSignal(item, date); } } void AgendaView::slotShowIncidence(const KCalCore::Incidence::Ptr &incidence) { - Akonadi::Item item = d->mViewCalendar->item(incidence); + Akonadi::Item item = viewCalendar()->item(incidence); if (item.isValid()) { emit showIncidenceSignal(item); } } void AgendaView::slotEditIncidence(const KCalCore::Incidence::Ptr &incidence, const KDateTime &occurrenceDate) { - Akonadi::Item item = d->mViewCalendar->item(incidence); + Akonadi::Item item = viewCalendar()->item(incidence); if (item.isValid()) { emit editIncidenceSignal(item, occurrenceDate); } } void AgendaView::slotDeleteIncidence(const KCalCore::Incidence::Ptr &incidence) { - Akonadi::Item item = d->mViewCalendar->item(incidence); + Akonadi::Item item = viewCalendar()->item(incidence); if (item.isValid()) { emit deleteIncidenceSignal(item); } } void AgendaView::zoomInVertically( ) { if ( !d->mIsSideBySide ) { preferences()->setHourSize( preferences()->hourSize() + 1 ); } d->mAgenda->updateConfig(); d->mAgenda->checkScrollBoundaries(); d->mTimeLabelsZone->updateAll(); setChanges( changes() | ZoomChanged ); updateView(); } void AgendaView::zoomOutVertically( ) { if ( preferences()->hourSize() > 4 || d->mIsSideBySide ) { if ( !d->mIsSideBySide ) { preferences()->setHourSize( preferences()->hourSize() - 1 ); } d->mAgenda->updateConfig(); d->mAgenda->checkScrollBoundaries(); d->mTimeLabelsZone->updateAll(); setChanges( changes() | ZoomChanged ); updateView(); } } void AgendaView::zoomInHorizontally( const QDate &date ) { QDate begin; QDate newBegin; QDate dateToZoom = date; int ndays, count; begin = d->mSelectedDates.first(); ndays = begin.daysTo( d->mSelectedDates.last() ); // zoom with Action and are there a selected Incidence?, Yes, I zoom in to it. if ( ! dateToZoom.isValid () ) { dateToZoom = d->mAgenda->selectedIncidenceDate(); } if ( !dateToZoom.isValid() ) { if ( ndays > 1 ) { newBegin = begin.addDays(1); count = ndays - 1; emit zoomViewHorizontally ( newBegin, count ); } } else { if ( ndays <= 2 ) { newBegin = dateToZoom; count = 1; } else { newBegin = dateToZoom.addDays( -ndays / 2 + 1 ); count = ndays -1 ; } emit zoomViewHorizontally ( newBegin, count ); } } void AgendaView::zoomOutHorizontally( const QDate &date ) { QDate begin; QDate newBegin; QDate dateToZoom = date; int ndays, count; begin = d->mSelectedDates.first(); ndays = begin.daysTo( d->mSelectedDates.last() ); // zoom with Action and are there a selected Incidence?, Yes, I zoom out to it. if ( ! dateToZoom.isValid () ) { dateToZoom = d->mAgenda->selectedIncidenceDate(); } if ( !dateToZoom.isValid() ) { newBegin = begin.addDays( -1 ); count = ndays + 3 ; } else { newBegin = dateToZoom.addDays( -ndays / 2 - 1 ); count = ndays + 3; } if ( abs( count ) >= 31 ) { kDebug() << "change to the month view?"; } else { //We want to center the date emit zoomViewHorizontally( newBegin, count ); } } void AgendaView::zoomView( const int delta, const QPoint &pos, const Qt::Orientation orient ) { // TODO find out why this is necessary. seems to be some kind of performance hack static QDate zoomDate; static QTimer *t = new QTimer( this ); //Zoom to the selected incidence, on the other way // zoom to the date on screen after the first mousewheel move. if ( orient == Qt::Horizontal ) { const QDate date = d->mAgenda->selectedIncidenceDate(); if ( date.isValid() ) { zoomDate=date; } else { if ( !t->isActive() ) { zoomDate= d->mSelectedDates[ pos.x() ]; } t->setSingleShot( true ); t->start ( 1000 ); } if ( delta > 0 ) { zoomOutHorizontally( zoomDate ); } else { zoomInHorizontally( zoomDate ); } } else { // Vertical zoom const QPoint posConstentsOld = d->mAgenda->gridToContents( pos ); if ( delta > 0 ) { zoomOutVertically(); } else { zoomInVertically(); } const QPoint posConstentsNew = d->mAgenda->gridToContents( pos ); d->mAgenda->verticalScrollBar()->scroll( 0, posConstentsNew.y() - posConstentsOld.y() ); } } #ifndef EVENTVIEWS_NODECOS bool AgendaView::loadDecorations( const QStringList &decorations, DecorationList &decoList ) { foreach ( const QString &decoName, decorations ) { if ( preferences()->selectedPlugins().contains( decoName ) ) { decoList << d->loadCalendarDecoration( decoName ); } } return decoList.count() > 0; } void AgendaView::placeDecorationsFrame( KHBox *frame, bool decorationsFound, bool isTop ) { if ( decorationsFound ) { if ( isTop ) { // inserts in the first position d->mSplitterAgenda->insertWidget( 0, frame ); } else { // inserts in the last position frame->setParent( d->mSplitterAgenda ); } } else { frame->setParent( this ); d->mGridLayout->addWidget( frame, 0, 0 ); } } void AgendaView::placeDecorations( DecorationList &decoList, const QDate &date, KHBox *labelBox, bool forWeek ) { foreach ( CalendarDecoration::Decoration *deco, decoList ) { CalendarDecoration::Element::List elements; elements = forWeek ? deco->weekElements( date ) : deco->dayElements( date ); if ( elements.count() > 0 ) { KHBox *decoHBox = new KHBox( labelBox ); decoHBox->setFrameShape( QFrame::StyledPanel ); decoHBox->setMinimumWidth( 1 ); foreach ( CalendarDecoration::Element *it, elements ) { DecorationLabel *label = new DecorationLabel( it, decoHBox ); label->setAlignment( Qt::AlignBottom ); label->setMinimumWidth( 1 ); } } } } #endif void AgendaView::createDayLabels( bool force ) { // Check if mSelectedDates has changed, if not just return // Removes some flickering and gains speed (since this is called by each updateView()) if ( !force && d->mSaveSelectedDates == d->mSelectedDates ) { return; } d->mSaveSelectedDates = d->mSelectedDates; delete d->mTopDayLabels; delete d->mBottomDayLabels; d->mDateDayLabels.clear(); QFontMetrics fm = fontMetrics(); d->mTopDayLabels = new QFrame ( d->mTopDayLabelsFrame ); d->mTopDayLabelsFrame->setStretchFactor( d->mTopDayLabels, 1 ); d->mLayoutTopDayLabels = new QHBoxLayout( d->mTopDayLabels ); d->mLayoutTopDayLabels->setMargin( 0 ); d->mLayoutTopDayLabels->setSpacing( 1 ); // this spacer moves the day labels over to line up with the day columns QSpacerItem *spacer = new QSpacerItem( ( !d->mIsSideBySide ? d->mTimeLabelsZone->width() : 0 ) + SPACING + d->mAllDayAgenda->scrollArea()->frameWidth(), 1, QSizePolicy::Fixed ); d->mLayoutTopDayLabels->addSpacerItem( spacer ); KVBox *topWeekLabelBox = new KVBox( d->mTopDayLabels ); d->mLayoutTopDayLabels->addWidget( topWeekLabelBox ); if ( d->mIsSideBySide ) { topWeekLabelBox->hide(); } d->mBottomDayLabels = new QFrame( d->mBottomDayLabelsFrame ); d->mBottomDayLabelsFrame->setStretchFactor( d->mBottomDayLabels, 1 ); d->mLayoutBottomDayLabels = new QHBoxLayout( d->mBottomDayLabels ); d->mLayoutBottomDayLabels->setMargin( 0 ); KVBox *bottomWeekLabelBox = new KVBox( d->mBottomDayLabels ); d->mLayoutBottomDayLabels->addWidget( bottomWeekLabelBox ); const KCalendarSystem *calsys = KGlobal::locale()->calendar(); #ifndef EVENTVIEWS_NODECOS QList topDecos; QStringList topStrDecos = preferences()->decorationsAtAgendaViewTop(); placeDecorationsFrame( d->mTopDayLabelsFrame, loadDecorations( topStrDecos, topDecos ), true ); QList botDecos; QStringList botStrDecos = preferences()->decorationsAtAgendaViewBottom(); placeDecorationsFrame( d->mBottomDayLabelsFrame, loadDecorations( botStrDecos, botDecos ), false ); #endif Q_FOREACH ( const QDate &date, d->mSelectedDates ) { KVBox *topDayLabelBox = new KVBox( d->mTopDayLabels ); d->mLayoutTopDayLabels->addWidget( topDayLabelBox ); KVBox *bottomDayLabelBox = new KVBox( d->mBottomDayLabels ); d->mLayoutBottomDayLabels->addWidget( bottomDayLabelBox ); int dW = calsys->dayOfWeek( date ); QString veryLongStr = KGlobal::locale()->formatDate( date ); QString longstr = i18nc( "short_weekday date (e.g. Mon 13)","%1 %2", calsys->weekDayName( dW, KCalendarSystem::ShortDayName ), calsys->day( date ) ); QString shortstr = QString::number( calsys->day( date ) ); AlternateLabel *dayLabel = new AlternateLabel( shortstr, longstr, veryLongStr, topDayLabelBox ); dayLabel->useShortText(); // will be recalculated in updateDayLabelSizes() anyway dayLabel->setMinimumWidth( 1 ); dayLabel->setAlignment( Qt::AlignHCenter ); if ( date == QDate::currentDate() ) { QFont font = dayLabel->font(); font.setBold( true ); dayLabel->setFont( font ); } d->mDateDayLabels.append( dayLabel ); // if a holiday region is selected, show the holiday name const QStringList texts = CalendarSupport::holiday( date ); Q_FOREACH ( const QString &text, texts ) { // Compute a small version of the holiday string for AlternateLabel const KWordWrap *ww = KWordWrap::formatText( fm, topDayLabelBox->rect(), 0, text, -1 ); AlternateLabel *label = new AlternateLabel( ww->truncatedString(), text, text, topDayLabelBox ); label->setMinimumWidth( 1 ); label->setAlignment( Qt::AlignCenter ); delete ww; } #ifndef EVENTVIEWS_NODECOS // Day decoration labels placeDecorations( topDecos, date, topDayLabelBox, false ); placeDecorations( botDecos, date, bottomDayLabelBox, false ); #endif } QSpacerItem *rightSpacer = new QSpacerItem( d->mAllDayAgenda->scrollArea()->frameWidth(), 1, QSizePolicy::Fixed ); d->mLayoutTopDayLabels->addSpacerItem( rightSpacer ); #ifndef EVENTVIEWS_NODECOS // Week decoration labels placeDecorations( topDecos, d->mSelectedDates.first(), topWeekLabelBox, true ); placeDecorations( botDecos, d->mSelectedDates.first(), bottomWeekLabelBox, true ); #endif if ( !d->mIsSideBySide ) { d->mLayoutTopDayLabels->addSpacing( d->mAgenda->verticalScrollBar()->width() ); d->mLayoutBottomDayLabels->addSpacing( d->mAgenda->verticalScrollBar()->width() ); } d->mTopDayLabels->show(); d->mBottomDayLabels->show(); // Update the labels now and after a single event loop run. Now to avoid flicker, and // delayed so that the delayed layouting size is taken into account. updateDayLabelSizes(); } void AgendaView::updateDayLabelSizes() { // First, calculate the maximum text type that fits for all labels AlternateLabel::TextType overallType = AlternateLabel::Extensive; foreach ( AlternateLabel *label, d->mDateDayLabels ) { AlternateLabel::TextType type = label->largestFittingTextType(); if ( type < overallType ) { overallType = type; } } // Then, set that maximum text type to all the labels foreach ( AlternateLabel *label, d->mDateDayLabels ) { label->setFixedType( overallType ); } } void AgendaView::resizeEvent( QResizeEvent *resizeEvent ) { updateDayLabelSizes(); EventView::resizeEvent( resizeEvent ); } void AgendaView::enableAgendaUpdate( bool enable ) { d->mAllowAgendaUpdate = enable; } int AgendaView::currentDateCount() const { return d->mSelectedDates.count(); } Akonadi::Item::List AgendaView::selectedIncidences() const { Akonadi::Item::List selected; KCalCore::Incidence::Ptr agendaitem = d->mAgenda->selectedIncidence(); if ( agendaitem ) { - selected.append( d->mViewCalendar->item(agendaitem) ); + selected.append( viewCalendar()->item(agendaitem) ); } KCalCore::Incidence::Ptr dayitem = d->mAllDayAgenda->selectedIncidence(); if ( dayitem ) { - selected.append( d->mViewCalendar->item(dayitem) ); + selected.append( viewCalendar()->item(dayitem) ); } return selected; } KCalCore::DateList AgendaView::selectedIncidenceDates() const { KCalCore::DateList selected; QDate qd; qd = d->mAgenda->selectedIncidenceDate(); if ( qd.isValid() ) { selected.append( qd ); } qd = d->mAllDayAgenda->selectedIncidenceDate(); if ( qd.isValid() ) { selected.append( qd ); } return selected; } bool AgendaView::eventDurationHint( QDateTime &startDt, QDateTime &endDt, bool &allDay ) const { if ( selectionStart().isValid() ) { QDateTime start = selectionStart(); QDateTime end = selectionEnd(); if ( start.secsTo( end ) == 15 * 60 ) { // One cell in the agenda view selected, e.g. // because of a double-click, => Use the default duration QTime defaultDuration( CalendarSupport::KCalPrefs::instance()->defaultDuration().time() ); int addSecs = ( defaultDuration.hour() * 3600 ) + ( defaultDuration.minute() * 60 ); end = start.addSecs( addSecs ); } startDt = start; endDt = end; allDay = selectedIsAllDay(); return true; } return false; } /** returns if only a single cell is selected, or a range of cells */ bool AgendaView::selectedIsSingleCell() const { if ( !selectionStart().isValid() || !selectionEnd().isValid() ) { return false; } if ( selectedIsAllDay() ) { int days = selectionStart().daysTo( selectionEnd() ); return ( days < 1 ); } else { int secs = selectionStart().secsTo( selectionEnd() ); return ( secs <= 24 * 60 * 60 / d->mAgenda->rows() ); } } void AgendaView::updateView() { fillAgenda(); } /* Update configuration settings for the agenda view. This method is not complete. */ void AgendaView::updateConfig() { // Agenda can be null if setPreferences() is called inside the ctor // We don't need to update anything in this case. if ( d->mAgenda && d->mAllDayAgenda ) { d->mAgenda->updateConfig(); d->mAllDayAgenda->updateConfig(); d->mTimeLabelsZone->setPreferences( preferences() ); d->mTimeLabelsZone->updateAll(); updateTimeBarWidth(); setHolidayMasks(); createDayLabels( true ); setChanges( changes() | ConfigChanged ); updateView(); } } void AgendaView::createTimeBarHeaders() { qDeleteAll( d->mTimeBarHeaders ); d->mTimeBarHeaders.clear(); const QFont oldFont( font() ); QFont labelFont = d->mTimeLabelsZone->preferences()->agendaTimeLabelsFont(); labelFont.setPointSize( labelFont.pointSize() - SHRINKDOWN ); foreach ( QScrollArea *area, d->mTimeLabelsZone->timeLabels() ) { TimeLabels *timeLabel = static_cast( area->widget() ); QLabel *label = new QLabel( timeLabel->header().replace( QLatin1Char('/'), QLatin1String("/ ") ), d->mTimeBarHeaderFrame ); label->setFont( labelFont ); label->setAlignment( Qt::AlignBottom | Qt::AlignRight ); label->setMargin( 0 ); label->setWordWrap( true ); label->setToolTip( timeLabel->headerToolTip() ); d->mTimeBarHeaders.append( label ); } setFont( oldFont ); } void AgendaView::updateTimeBarWidth() { if ( d->mIsSideBySide ) { return; } createTimeBarHeaders(); const QFont oldFont( font() ); QFont labelFont = d->mTimeLabelsZone->preferences()->agendaTimeLabelsFont(); labelFont.setPointSize( labelFont.pointSize() - SHRINKDOWN ); QFontMetrics fm( labelFont ); int width = d->mTimeLabelsZone->preferedTimeLabelsWidth(); foreach ( QLabel *l, d->mTimeBarHeaders ) { foreach ( const QString &word, l->text().split( QLatin1Char(' ') ) ) { width = qMax( width, fm.width( word ) ); } } setFont( oldFont ); width = width + fm.width( QLatin1Char( '/' ) ); const int timeBarWidth = width * d->mTimeBarHeaders.count(); d->mTimeBarHeaderFrame->setFixedWidth( timeBarWidth - SPACING ); d->mTimeLabelsZone->setFixedWidth( timeBarWidth ); d->mDummyAllDayLeft->setFixedWidth( 0 ); } void AgendaView::updateEventDates( AgendaItem *item, bool addIncidence, Akonadi::Collection::Id collectionId ) { kDebug() << item->text() << "; item->cellXLeft(): " << item->cellXLeft() << "; item->cellYTop(): " << item->cellYTop() << "; item->lastMultiItem(): " << item->lastMultiItem() << "; item->itemPos(): " << item->itemPos() << "; item->itemCount(): " << item->itemCount() << endl; KDateTime startDt, endDt; // Start date of this incidence, calculate the offset from it // (so recurring and non-recurring items can be treated exactly the same, // we never need to check for recurs(), because we only move the start day // by the number of days the agenda item was really moved. Smart, isn't it?) QDate thisDate; if ( item->cellXLeft() < 0 ) { thisDate = ( d->mSelectedDates.first() ).addDays( item->cellXLeft() ); } else { thisDate = d->mSelectedDates[ item->cellXLeft() ]; } int daysOffset = 0; // daysOffset should only be calculated if item->cellXLeft() is positive which doesn't happen // if the event's start isn't visible. if ( item->cellXLeft() >= 0 ) { daysOffset = item->occurrenceDate().daysTo( thisDate ); } int daysLength = 0; // startDt.setDate( startDate ); KCalCore::Incidence::Ptr incidence = item->incidence(); - Akonadi::Item aitem = d->mViewCalendar->item(incidence); + Akonadi::Item aitem = viewCalendar()->item(incidence); if ( (!aitem.isValid() && !addIncidence) || !incidence || !changer() ) { kWarning() << "changer is " << changer() << " and incidence is " << incidence.data(); return; } QTime startTime( 0, 0, 0 ), endTime( 0, 0, 0 ); if ( incidence->allDay() ) { daysLength = item->cellWidth() - 1; } else { startTime = d->mAgenda->gyToTime( item->cellYTop() ); if ( item->lastMultiItem() ) { endTime = d->mAgenda->gyToTime( item->lastMultiItem()->cellYBottom() + 1 ); daysLength = item->lastMultiItem()->cellXLeft() - item->cellXLeft(); } else if ( item->itemPos() == item->itemCount() && item->itemCount() > 1 ) { /* multiitem handling in agenda assumes two things: - The start (first KOAgendaItem) is always visible. - The first KOAgendaItem of the incidence has a non-null item->lastMultiItem() pointing to the last KOagendaItem. But those aren't always met, for example when in day-view. kolab/issue4417 */ // Cornercase 1: - Resizing the end of the event but the start isn't visible endTime = d->mAgenda->gyToTime( item->cellYBottom() + 1 ); daysLength = item->itemCount() - 1; startTime = incidence->dtStart().time(); } else if ( item->itemPos() == 1 && item->itemCount() > 1 ) { // Cornercase 2: - Resizing the start of the event but the end isn't visible endTime = incidence->dateTime( KCalCore::Incidence::RoleEnd ).time(); daysLength = item->itemCount() - 1; } else { endTime = d-> mAgenda->gyToTime( item->cellYBottom() + 1 ); } } // FIXME: use a visitor here if ( const KCalCore::Event::Ptr ev = CalendarSupport::event( incidence ) ) { startDt = incidence->dtStart(); // convert to calendar timespec because we then manipulate it // with time coming from the calendar startDt = startDt.toTimeSpec( preferences()->timeSpec() ); startDt = startDt.addDays( daysOffset ); if ( !startDt.isDateOnly() ) { startDt.setTime( startTime ); } endDt = startDt.addDays( daysLength ); if ( !endDt.isDateOnly() ) { endDt.setTime( endTime ); } if ( incidence->dtStart().toTimeSpec( preferences()->timeSpec() ) == startDt && ev->dtEnd().toTimeSpec( preferences()->timeSpec() ) == endDt ) { // No change QTimer::singleShot( 0, this, SLOT(updateView()) ); return; } } else if ( const KCalCore::Todo::Ptr td = CalendarSupport::todo( incidence ) ) { startDt = td->hasStartDate() ? td->dtStart() : td->dtDue(); // convert to calendar timespec because we then manipulate it with time coming from // the calendar startDt = startDt.toTimeSpec( preferences()->timeSpec() ); startDt.setDate( thisDate.addDays( td->dtDue().daysTo( startDt ) ) ); if ( !startDt.isDateOnly() ) { startDt.setTime( startTime ); } endDt = startDt; endDt.setDate( thisDate ); if ( !endDt.isDateOnly() ) { endDt.setTime( endTime ); } if ( td->dtDue().toTimeSpec( preferences()->timeSpec() ) == endDt ) { // No change QMetaObject::invokeMethod( this, "updateView", Qt::QueuedConnection ); return; } } // A commented code block which had 150 lines to adjust recurrence was here. // I deleted it in rev 1180272 to make this function readable. if ( const KCalCore::Event::Ptr ev = CalendarSupport::event( incidence ) ) { /* setDtEnd() must be called before setDtStart(), otherwise, when moving * events, CalendarLocal::incidenceUpdated() will not remove the old hash * and that causes the event to be shown in the old date also (bug #179157). * * TODO: We need a better hashing mechanism for CalendarLocal. */ ev->setDtEnd( endDt.toTimeSpec( incidence->dateTime( KCalCore::Incidence::RoleEnd ).timeSpec() ) ); incidence->setDtStart( startDt.toTimeSpec( incidence->dtStart().timeSpec() ) ); } else if ( const KCalCore::Todo::Ptr td = CalendarSupport::todo( incidence ) ) { if ( td->hasStartDate() ) { td->setDtStart( startDt.toTimeSpec( incidence->dtStart().timeSpec() ) ); } td->setDtDue( endDt.toTimeSpec( td->dtDue().timeSpec() ) ); } if (!incidence->hasRecurrenceId()) { item->setOccurrenceDateTime( startDt ); } bool result; if ( addIncidence ) { - Akonadi::Collection collection = calendar()->collection( collectionId ); + Akonadi::Collection collection = etmCalendar()->collection( collectionId ); + kDebug() << "Collection isValid() = " << collection.isValid(); result = changer()->createIncidence( incidence, collection, this ) != -1; } else { KCalCore::Incidence::Ptr oldIncidence( CalendarSupport::incidence(aitem) ); aitem.setPayload(incidence); result = changer()->modifyIncidence( aitem, oldIncidence, this ) != -1; } // Update the view correctly if an agenda item move was aborted by // cancelling one of the subsequent dialogs. if ( !result ) { setChanges( changes() | IncidencesEdited ); QMetaObject::invokeMethod( this, "updateView", Qt::QueuedConnection ); return; } // don't update the agenda as the item already has the correct coordinates. // an update would delete the current item and recreate it, but we are still // using a pointer to that item! => CRASH enableAgendaUpdate( false ); // We need to do this in a timer to make sure we are not deleting the item // we are currently working on, which would lead to crashes // Only the actually moved agenda item is already at the correct position and mustn't be // recreated. All others have to!!! if ( incidence->recurs() || incidence->hasRecurrenceId() ) { d->mUpdateItem = aitem; QMetaObject::invokeMethod( this, "updateView", Qt::QueuedConnection ); } enableAgendaUpdate( true ); } QDate AgendaView::startDate() const { if ( d->mSelectedDates.isEmpty() ) { return QDate(); } return d->mSelectedDates.first(); } QDate AgendaView::endDate() const { if ( d->mSelectedDates.isEmpty() ) { return QDate(); } return d->mSelectedDates.last(); } void AgendaView::showDates( const QDate &start, const QDate &end, const QDate &preferredMonth ) { Q_UNUSED( preferredMonth ); if ( !d->mSelectedDates.isEmpty() && d->mSelectedDates.first() == start && d->mSelectedDates.last() == end ) { return; } if ( !start.isValid() || !end.isValid() || start > end || start.daysTo( end ) > MAX_DAY_COUNT ) { kWarning() << "got bizare parameters: " << start << end << " - aborting here"; return; } d->mSelectedDates = d->generateDateList( start, end ); // and update the view setChanges( changes() | DatesChanged ); fillAgenda(); } void AgendaView::showIncidences( const Akonadi::Item::List &incidences, const QDate &date ) { Q_UNUSED( date ); - if ( !calendar() ) { + if ( !calendars() ) { kError() << "No Calendar set"; return; } // we must check if they are not filtered; if they are, remove the filter - KCalCore::CalFilter *filter = calendar()->filter(); + KCalCore::CalFilter *filter = etmCalendar()->filter(); bool wehaveall = true; if ( filter ) { Q_FOREACH ( const Akonadi::Item &aitem, incidences ) { if ( !( wehaveall = filter->filterIncidence( CalendarSupport::incidence( aitem ) ) ) ) { break; } } } if ( !wehaveall ) { - calendar()->setFilter( 0 ); + etmCalendar()->setFilter( 0 ); } const KDateTime::Spec timeSpec = preferences()->timeSpec(); KDateTime start = CalendarSupport::incidence( incidences.first() )->dtStart().toTimeSpec( timeSpec ); KDateTime end = CalendarSupport::incidence( incidences.first() )->dateTime( KCalCore::Incidence::RoleEnd ).toTimeSpec( timeSpec ); Akonadi::Item first = incidences.first(); Q_FOREACH ( const Akonadi::Item &aitem, incidences ) { if ( CalendarSupport::incidence( aitem )->dtStart().toTimeSpec( timeSpec ) < start ) { first = aitem; } start = qMin( start, CalendarSupport::incidence( aitem )->dtStart().toTimeSpec( timeSpec ) ); end = qMax( start, CalendarSupport::incidence( aitem )->dateTime( KCalCore::Incidence::RoleEnd ).toTimeSpec( timeSpec ) ); } end.toTimeSpec( start ); // allow direct comparison of dates if ( start.date().daysTo( end.date() ) + 1 <= currentDateCount() ) { showDates( start.date(), end.date() ); } else { showDates( start.date(), start.date().addDays( currentDateCount() - 1 ) ); } - d->mAgenda->selectItem( first ); + d->mAgenda->selectItem( CalendarSupport::incidence(first) ); } void AgendaView::fillAgenda() { if ( changes() == NothingChanged ) { return; } - if ( d->mViewCalendar->calendars() == 0) { + if ( viewCalendar()->calendars() == 0) { kWarning() << "No calendar is set"; return; } /* kDebug() << "changes = " << changes() << "; mUpdateAgenda = " << d->mUpdateAgenda << "; mUpdateAllDayAgenda = " << d->mUpdateAllDayAgenda; */ /* Remember the item Ids of the selected items. In case one of the * items was deleted and re-added, we want to reselect it. */ const QString selectedAgendaId = d->mAgenda->lastSelectedItemId(); const QString selectedAllDayAgendaId = d->mAllDayAgenda->lastSelectedItemId(); enableAgendaUpdate( true ); d->clearView(); if ( changes().testFlag( DatesChanged ) ) { d->mAllDayAgenda->changeColumns( d->mSelectedDates.count() ); d->mAgenda->changeColumns( d->mSelectedDates.count() ); d->changeColumns( d->mSelectedDates.count() ); createDayLabels( false ); setHolidayMasks(); d->mAgenda->setDateList( d->mSelectedDates ); } setChanges( NothingChanged ); bool somethingReselected = false; - const KCalCore::Incidence::List incidences = d->mViewCalendar->incidences(); + const KCalCore::Incidence::List incidences = viewCalendar()->incidences(); foreach ( const KCalCore::Incidence::Ptr &incidence, incidences ) { Q_ASSERT( incidence ); - const bool wasSelected = incidence->uid() == selectedAgendaId || - incidence->uid() == selectedAllDayAgendaId; + const bool wasSelected = viewCalendar()->uid(incidence) == selectedAgendaId || + viewCalendar()->uid(incidence) == selectedAllDayAgendaId; if ( ( incidence->allDay() && d->mUpdateAllDayAgenda ) || ( !incidence->allDay() && d->mUpdateAgenda ) ) { displayIncidence( incidence, wasSelected ); } if ( wasSelected ) { somethingReselected = true; } } d->mAgenda->checkScrollBoundaries(); updateEventIndicators(); // mAgenda->viewport()->update(); // mAllDayAgenda->viewport()->update(); // make invalid deleteSelectedDateTime(); d->mUpdateAgenda = false; d->mUpdateAllDayAgenda = false; if ( !somethingReselected ) { emit incidenceSelected( Akonadi::Item(), QDate() ); } } bool AgendaView::displayIncidence( const KCalCore::Incidence::Ptr &incidence, bool createSelected ) { if ( !incidence || incidence->hasRecurrenceId() ) { return false; } KCalCore::Todo::Ptr todo = CalendarSupport::todo( incidence ); if ( todo && ( !preferences()->showTodosAgendaView() || !todo->hasDueDate() ) ) { return false; } KCalCore::Event::Ptr event = CalendarSupport::event( incidence ); const QDate today = QDate::currentDate(); KCalCore::DateTimeList::iterator t; const KDateTime::Spec timeSpec = preferences()->timeSpec(); KDateTime firstVisibleDateTime( d->mSelectedDates.first(), timeSpec ); KDateTime lastVisibleDateTime( d->mSelectedDates.last(), timeSpec ); // Optimization, very cheap operation that discards incidences that aren't in the timespan if ( !d->mightBeVisible( incidence ) ) { return false; } lastVisibleDateTime.setTime( QTime( 23, 59, 59, 59 ) ); firstVisibleDateTime.setTime( QTime( 0, 0 ) ); KCalCore::DateTimeList dateTimeList; const KDateTime incDtStart = incidence->dtStart().toTimeSpec( timeSpec ); const KDateTime incDtEnd = incidence->dateTime( KCalCore::Incidence::RoleEnd ).toTimeSpec( timeSpec ); bool alreadyAddedToday = false; if ( incidence->recurs() ) { // timed incidences occur in [dtStart(), dtEnd()[ // all-day incidences occur in [dtStart(), dtEnd()] // so we subtract 1 second in the timed case const int secsToAdd = incidence->allDay() ? 0 : -1; const int eventDuration = event ? incDtStart.daysTo( incDtEnd.addSecs( secsToAdd ) ) : 0; // if there's a multiday event that starts before firstVisibleDateTime but ends after // lets include it. timesInInterval() ignores incidences that aren't totaly inside // the range const KDateTime startDateTimeWithOffset = firstVisibleDateTime.addDays( -eventDuration ); - KCalCore::OccurrenceIterator rIt( *calendar(), incidence, + KCalCore::OccurrenceIterator rIt( *calendar(incidence), incidence, startDateTimeWithOffset, lastVisibleDateTime ); while ( rIt.hasNext() ) { rIt.next(); const KDateTime occurrenceDate( rIt.occurrenceStartDate().toTimeSpec( timeSpec ) ); const bool makesDayBusy = preferences()->colorAgendaBusyDays() && makesWholeDayBusy( rIt.incidence() ); if ( makesDayBusy ) { KCalCore::Event::List &busyEvents = d->mBusyDays[occurrenceDate.date()]; busyEvents.append( event ); } + /* + const Akonadi::Item item = etmCalendar()->item( rIt.incidence() ); + if ( !item.isValid() ) { + kWarning() << "Couldn't find item for " + << rIt.incidence()->uid() << rIt.incidence()->recurrenceId().toString(); + continue; + } + Q_ASSERT( item.hasPayload() ); */ if ( occurrenceDate.date() == today ) { alreadyAddedToday = true; } d->insertIncidence( rIt.incidence(), rIt.recurrenceId(), occurrenceDate, createSelected ); } } else { KDateTime dateToAdd; // date to add to our date list KDateTime incidenceStart; KDateTime incidenceEnd; if ( todo && todo->hasDueDate() && !todo->isOverdue() ) { // If it's not overdue it will be shown at the original date (not today) dateToAdd = todo->dtDue().toTimeSpec( timeSpec ); // To-dos are drawn with the bottom of the rectangle at dtDue // if dtDue is at 00:00, then it should be displayed in the previous day, at 23:59 if ( dateToAdd.time() == QTime( 0, 0 ) ) { dateToAdd = dateToAdd.addSecs( -1 ); } incidenceEnd = dateToAdd; } else if ( event ) { dateToAdd = incDtStart; incidenceEnd = incDtEnd; } if ( dateToAdd.isValid() && dateToAdd.isDateOnly() ) { // so comparisons with < > actually work dateToAdd.setTime( QTime( 0, 0 ) ); incidenceEnd.setTime( QTime( 23, 59, 59, 59 ) ); } if ( dateToAdd <= lastVisibleDateTime && incidenceEnd > firstVisibleDateTime ) { dateTimeList += dateToAdd; } } // ToDo items shall be displayed today if they are overdue const KDateTime dateTimeToday = KDateTime( today, timeSpec ); if ( todo && todo->isOverdue() && dateTimeToday >= firstVisibleDateTime && dateTimeToday <= lastVisibleDateTime ) { /* If there's a recurring instance showing up today don't add "today" again * we don't want the event to appear duplicated */ if ( !alreadyAddedToday ) { dateTimeList += dateTimeToday; } } const bool makesDayBusy = preferences()->colorAgendaBusyDays() && makesWholeDayBusy( incidence ); for ( t = dateTimeList.begin(); t != dateTimeList.end(); ++t ) { if ( makesDayBusy ) { KCalCore::Event::List &busyEvents = d->mBusyDays[(*t).date()]; busyEvents.append( event ); } d->insertIncidence( incidence, t->toTimeSpec( timeSpec ), t->toTimeSpec( timeSpec ), createSelected ); } // Can be multiday if ( event && makesDayBusy && event->isMultiDay() ) { const QDate lastVisibleDate = d->mSelectedDates.last(); for ( QDate date = event->dtStart().date(); date <= event->dtEnd().date() && date <= lastVisibleDate ; date = date.addDays( 1 ) ) { KCalCore::Event::List &busyEvents = d->mBusyDays[date]; busyEvents.append( event ); } } return !dateTimeList.isEmpty(); } void AgendaView::updateEventIndicatorTop( int newY ) { for ( int i = 0; i < d->mMinY.size(); ++i ) { d->mEventIndicatorTop->enableColumn( i, newY > d->mMinY[i] ); } d->mEventIndicatorTop->update(); } void AgendaView::updateEventIndicatorBottom( int newY ) { for ( int i = 0; i < d->mMaxY.size(); ++i ) { d->mEventIndicatorBottom->enableColumn( i, newY <= d->mMaxY[i] ); } d->mEventIndicatorBottom->update(); } void AgendaView::slotIncidencesDropped( const QList &items, const QPoint &gpos, bool allDay ) { Q_UNUSED( items ); Q_UNUSED( gpos ); Q_UNUSED( allDay ); #ifdef AKONADI_PORT_DISABLED // one item -> multiple items, Incidence* -> akonadi item url (we might have to fetch the items here first!) if ( gpos.x() < 0 || gpos.y() < 0 ) { return; } const QDate day = d->mSelectedDates[gpos.x()]; const QTime time = d->mAgenda->gyToTime( gpos.y() ); KDateTime newTime( day, time, preferences()->timeSpec() ); newTime.setDateOnly( allDay ); Todo::Ptr todo = CalendarSupport::todo( todoItem ); if ( todo && dynamic_cast( calendar() ) ) { const Akonadi::Item existingTodoItem = calendar()->itemForIncidence( calendar()->todo( todo->uid() ) ); if ( Todo::Ptr existingTodo = CalendarSupport::todo( existingTodoItem ) ) { kDebug() << "Drop existing Todo"; Todo::Ptr oldTodo( existingTodo->clone() ); if ( changer() ) { existingTodo->setDtDue( newTime ); existingTodo->setAllDay( allDay ); changer()->modifyIncidence( existingTodoItem, oldTodo, this ); } else { KMessageBox::sorry( this, i18n( "Unable to modify this to-do, " "because it cannot be locked." ) ); } } else { kDebug() << "Drop new Todo"; todo->setDtDue( newTime ); todo->setAllDay( allDay ); if ( !changer()->addIncidence( todo, this ) ) { KMessageBox::sorry( this, i18n( "Unable to save %1 \"%2\".", i18n( todo->type() ), todo->summary() ) ); } } } #else kDebug() << "AKONADI PORT: Disabled code in " << Q_FUNC_INFO; #endif } void AgendaView::slotIncidencesDropped( const KCalCore::Incidence::List &incidences, const QPoint &gpos, bool allDay ) { if ( gpos.x() < 0 || gpos.y() < 0 ) { return; } const QDate day = d->mSelectedDates[gpos.x()]; const QTime time = d->mAgenda->gyToTime( gpos.y() ); KDateTime newTime( day, time, preferences()->timeSpec() ); newTime.setDateOnly( allDay ); Q_FOREACH ( const KCalCore::Incidence::Ptr &incidence, incidences ) { - const Akonadi::Item existingItem = calendar()->item( incidence ); + const Akonadi::Item existingItem = etmCalendar()->item( incidence ); const bool existsInSameCollection = existingItem.isValid() && ( existingItem.storageCollectionId() == collectionId() || collectionId() == -1 ); if ( existingItem.isValid() && existsInSameCollection ) { KCalCore::Incidence::Ptr newIncidence = existingItem.payload(); KCalCore::Incidence::Ptr oldIncidence( newIncidence->clone() ); if ( newIncidence->dtStart() == newTime && newIncidence->allDay() == allDay ) { // Nothing changed continue; } newIncidence->setAllDay( allDay ); newIncidence->setDateTime( newTime, KCalCore::Incidence::RoleDnD ); changer()->modifyIncidence( existingItem, oldIncidence, this ); } else { // Create a new one // The drop came from another application create a new incidence incidence->setDateTime( newTime, KCalCore::Incidence::RoleDnD ); incidence->setAllDay( allDay ); incidence->setUid( KCalCore::CalFormat::createUniqueId() ); Akonadi::Collection collection( collectionId() ); const bool added = -1 != changer()->createIncidence( incidence, collection, this ); if ( added ) { // TODO: make async if ( existingItem.isValid() ) { // Dragged from one agenda to another, delete origin changer()->deleteIncidence( existingItem ); } } } } } void AgendaView::startDrag(const KCalCore::Incidence::Ptr &incidence) { - if ( !calendar() ) { + if ( !calendars() ) { kError() << "No Calendar set"; return; } - const Akonadi::Item item = d->mViewCalendar->item(incidence); + const Akonadi::Item item = etmCalendar()->item(incidence); if (item.isValid()) { startDrag(item); } } void AgendaView::startDrag( const Akonadi::Item &incidence ) { - if ( !calendar() ) { + if ( !calendars() ) { kError() << "No Calendar set"; return; } #ifndef KORG_NODND - if ( QDrag *drag = CalendarSupport::createDrag( incidence, calendar()->timeSpec(), this ) ) { + if ( QDrag *drag = CalendarSupport::createDrag( incidence, etmCalendar()->timeSpec(), this ) ) { drag->exec(); } #else Q_UNUSED( incidence ); #endif } void AgendaView::readSettings() { readSettings( KGlobal::activeComponent().config().data() ); } void AgendaView::readSettings( const KConfig *config ) { const KConfigGroup group = config->group( "Views" ); const QList sizes = group.readEntry( "Separator AgendaView", QList() ); // the size depends on the number of plugins used // we don't want to read invalid/corrupted settings or else agenda becomes invisible if ( sizes.count() >= 2 && !sizes.contains( 0 ) ) { d->mSplitterAgenda->setSizes( sizes ); updateConfig(); } } void AgendaView::writeSettings( KConfig *config ) { KConfigGroup group = config->group( "Views" ); QList list = d->mSplitterAgenda->sizes(); group.writeEntry( "Separator AgendaView", list ); } QVector AgendaView::busyDayMask() const { if ( d->mSelectedDates.isEmpty() || !d->mSelectedDates[0].isValid() ) { return QVector(); } QVector busyDayMask; busyDayMask.resize( d->mSelectedDates.count() ); for ( int i = 0; i < d->mSelectedDates.count(); ++i ) { busyDayMask[i] = !d->mBusyDays[d->mSelectedDates[i]].isEmpty(); } return busyDayMask; } void AgendaView::setHolidayMasks() { if ( d->mSelectedDates.isEmpty() || !d->mSelectedDates[0].isValid() ) { return; } d->mHolidayMask.resize( d->mSelectedDates.count() + 1 ); const QList workDays = CalendarSupport::workDays( d->mSelectedDates.first().addDays( -1 ), d->mSelectedDates.last() ); for ( int i = 0; i < d->mSelectedDates.count(); ++i ) { d->mHolidayMask[i] = !workDays.contains( d->mSelectedDates[ i ] ); } // Store the information about the day before the visible area (needed for // overnight working hours) in the last bit of the mask: bool showDay = !workDays.contains( d->mSelectedDates[ 0 ].addDays( -1 ) ); d->mHolidayMask[ d->mSelectedDates.count() ] = showDay; d->mAgenda->setHolidayMask( &d->mHolidayMask ); d->mAllDayAgenda->setHolidayMask( &d->mHolidayMask ); } void AgendaView::clearSelection() { d->mAgenda->deselectItem(); d->mAllDayAgenda->deselectItem(); } void AgendaView::newTimeSpanSelectedAllDay( const QPoint &start, const QPoint &end ) { newTimeSpanSelected( start, end ); d->mTimeSpanInAllDay = true; } void AgendaView::newTimeSpanSelected( const QPoint &start, const QPoint &end ) { if ( !d->mSelectedDates.count() ) { return; } d->mTimeSpanInAllDay = false; const QDate dayStart = d-> mSelectedDates[ qBound( 0, start.x(), (int)d->mSelectedDates.size() - 1 ) ]; const QDate dayEnd = d->mSelectedDates[ qBound( 0, end.x(), (int)d->mSelectedDates.size() - 1 ) ]; const QTime timeStart = d->mAgenda->gyToTime( start.y() ); const QTime timeEnd = d->mAgenda->gyToTime( end.y() + 1 ); d->mTimeSpanBegin = QDateTime( dayStart, timeStart ); d->mTimeSpanEnd = QDateTime( dayEnd, timeEnd ); } QDateTime AgendaView::selectionStart() const { return d->mTimeSpanBegin; } QDateTime AgendaView::selectionEnd() const { return d->mTimeSpanEnd; } bool AgendaView::selectedIsAllDay() const { return d->mTimeSpanInAllDay; } void AgendaView::deleteSelectedDateTime() { d->mTimeSpanBegin.setDate( QDate() ); d->mTimeSpanEnd.setDate( QDate() ); d->mTimeSpanInAllDay = false; } void AgendaView::removeIncidence( const KCalCore::Incidence::Ptr &incidence ) { - // Don't wrap this in a if ( incidence->isAllDay ) because whe all day - // property might have changed - d->mAllDayAgenda->removeIncidence( incidence ); - d->mAgenda->removeIncidence( incidence ); - - if (!incidence->hasRecurrenceId() && d->mViewCalendar->isValid(incidence->uid())) { + if ( !incidence->hasRecurrenceId() && viewCalendar()->isValid(incidence)) { // Deleted incidence is an main incidence // Delete all exceptions as well - KCalCore::Incidence::List exceptions = calendar2(incidence->uid())->instances( incidence ); + KCalCore::Incidence::List exceptions = calendar(incidence)->instances( incidence ); foreach ( const KCalCore::Incidence::Ptr &exception, exceptions ) { if ( exception->allDay() ) { d->mAllDayAgenda->removeIncidence( exception ); } else { d->mAgenda->removeIncidence( exception ); } } } + + // Don't wrap this in a if ( incidence->isAllDay ) because whe all day + // property might have changed + d->mAllDayAgenda->removeIncidence( incidence ); + d->mAgenda->removeIncidence( incidence ); } void AgendaView::updateEventIndicators() { d->mUpdateEventIndicatorsScheduled = false; d->mMinY = d->mAgenda->minContentsY(); d->mMaxY = d->mAgenda->maxContentsY(); d->mAgenda->checkScrollBoundaries(); updateEventIndicatorTop( d->mAgenda->visibleContentsYMin() ); updateEventIndicatorBottom( d->mAgenda->visibleContentsYMax() ); } void AgendaView::setIncidenceChanger( Akonadi::IncidenceChanger *changer ) { EventView::setIncidenceChanger( changer ); d->mAgenda->setIncidenceChanger( changer ); d->mAllDayAgenda->setIncidenceChanger( changer ); } void AgendaView::clearTimeSpanSelection() { d->mAgenda->clearSelection(); d->mAllDayAgenda->clearSelection(); deleteSelectedDateTime(); } Agenda *AgendaView::agenda() const { return d->mAgenda; } Agenda *AgendaView::allDayAgenda() const { return d->mAllDayAgenda; } QSplitter *AgendaView::splitter() const { return d->mSplitterAgenda; } bool AgendaView::filterByCollectionSelection( const KCalCore::Incidence::Ptr &incidence ) { - const Akonadi::Item item = d->mViewCalendar->item(incidence); + const Akonadi::Item item = viewCalendar()->item(incidence); if (!item.isValid()) { return true; } if ( customCollectionSelection() ) { return customCollectionSelection()->contains(item.parentCollection().id() ); } if ( collectionId() < 0 ) { return true; } else { return collectionId() == item.storageCollectionId(); } } void AgendaView::alignAgendas() { // resize dummy widget so the allday agenda lines up with the hourly agenda. d->mDummyAllDayLeft->setFixedWidth( -SPACING + d->mTimeLabelsZone->width() - ( d->mIsSideBySide ? 0 : d->mTimeBarHeaderFrame->width() ) ); // Must be async, so they are centered createDayLabels( true ); } CalendarDecoration::Decoration *AgendaView::Private::loadCalendarDecoration( const QString &name ) { const QString type = CalendarSupport::Plugin::serviceType(); const int version = CalendarSupport::Plugin::interfaceVersion(); QString constraint; if ( version >= 0 ) { constraint = QString::fromLatin1( "[X-KDE-PluginInterfaceVersion] == %1" ).arg( QString::number( version ) ); } KService::List list = KServiceTypeTrader::self()->query( type, constraint ); KService::List::ConstIterator it; for ( it = list.constBegin(); it != list.constEnd(); ++it ) { if ( (*it)->desktopEntryName() == name ) { KService::Ptr service = *it; KPluginLoader loader( *service ); KPluginFactory *factory = loader.factory(); if ( !factory ) { kDebug() << "Factory creation failed"; return 0; } CalendarDecoration::DecorationFactory *pluginFactory = static_cast( factory ); if ( !pluginFactory ) { kDebug() << "Cast failed"; return 0; } return pluginFactory->createPluginFactory(); } } return 0; } void AgendaView::setChanges( EventView::Changes changes ) { d->setChanges( changes ); } void AgendaView::scheduleUpdateEventIndicators() { if ( !d->mUpdateEventIndicatorsScheduled ) { d->mUpdateEventIndicatorsScheduled = true; QTimer::singleShot( 0, this, SLOT(updateEventIndicators()) ); } } // kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/calendarviews/agenda/agendaview.h b/calendarviews/agenda/agendaview.h index 2ef46adfef..7799e353da 100644 --- a/calendarviews/agenda/agendaview.h +++ b/calendarviews/agenda/agendaview.h @@ -1,286 +1,279 @@ /* Copyright (c) 2000,2001,2003 Cornelius Schumacher Copyright (C) 2003-2004 Reinhold Kainhofer Copyright (C) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net Author: Kevin Krammer, krake@kdab.com Author: Sergio Martins, sergio.martins@kdab.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #ifndef EVENTVIEWS_AGENDAVIEW_H #define EVENTVIEWS_AGENDAVIEW_H #include "eventviews_export.h" #include "eventview.h" #include "viewcalendar.h" #include #include class KConfig; class KHBox; class QSplitter; namespace EventViews { #ifndef EVENTVIEWS_NODECOS namespace CalendarDecoration { class Decoration; } #endif class TimeLabels; class TimeLabelsZone; class Agenda; class AgendaItem; class AgendaView; class EVENTVIEWS_EXPORT EventIndicator : public QFrame { Q_OBJECT public: enum Location { Top, Bottom }; explicit EventIndicator( Location loc = Top, QWidget *parent = 0 ); virtual ~EventIndicator(); void changeColumns( int columns ); void enableColumn( int column, bool enable ); protected: void paintEvent( QPaintEvent *event ); bool eventFilter( QObject *, QEvent * ); private: class Private; Private *const d; }; /** AgendaView is the agenda-like view that displays events in a single or multi-day view. */ class EVENTVIEWS_EXPORT AgendaView : public EventView { Q_OBJECT public: explicit AgendaView( const PrefsPtr &preferences, const QDate &start, const QDate &end, bool isInteractive, bool isSideBySide = false, QWidget *parent = 0 ); explicit AgendaView( const QDate &start, const QDate &end, bool isInteractive, bool isSideBySide = false, QWidget *parent = 0 ); virtual ~AgendaView(); enum { MAX_DAY_COUNT = 42 // ( 6 * 7) }; /** Returns number of currently shown dates. */ virtual int currentDateCount() const; /** returns the currently selected events */ virtual Akonadi::Item::List selectedIncidences() const; /** returns the currently selected incidence's dates */ virtual KCalCore::DateList selectedIncidenceDates() const; /** return the default start/end date/time for new events */ virtual bool eventDurationHint( QDateTime &startDt, QDateTime &endDt, bool &allDay ) const; /** start-datetime of selection */ virtual QDateTime selectionStart() const; /** end-datetime of selection */ virtual QDateTime selectionEnd() const; /** returns true if selection is for whole day */ bool selectedIsAllDay() const; /** make selected start/end invalid */ void deleteSelectedDateTime(); /** returns if only a single cell is selected, or a range of cells */ bool selectedIsSingleCell() const; /* reimp from EventView */ virtual void setCalendar( const Akonadi::ETMCalendar::Ptr &cal ); + virtual void setCalendar( const MultiViewCalendar::Ptr &cal ); virtual void addCalendar( const ViewCalendar::Ptr &cal); QSplitter *splitter() const; // FIXME: we already have startDateTime() and endDateTime() in the base class /** First shown day */ QDate startDate() const; /** Last shown day */ QDate endDate() const; /** Update event belonging to agenda item If the incidence is multi-day, item is the first one */ void updateEventDates( AgendaItem *item, bool addIncidence, Akonadi::Collection::Id collectionId ); QVector busyDayMask() const; - /** - * Return calendar object for an congrete incidence. - * this function is able to use mutiple calenders - * TODO: replace EventsView::calendar() - */ - virtual KCalCore::Calendar::Ptr calendar2(KCalCore::Incidence::Ptr incidence) const; - virtual KCalCore::Calendar::Ptr calendar2(const QString &incidenceIdentifier) const; - public slots: virtual void updateView(); virtual void updateConfig(); virtual void showDates( const QDate &start, const QDate &end, const QDate &preferredMonth = QDate() ); virtual void showIncidences( const Akonadi::Item::List &incidenceList, const QDate &date ); void clearSelection(); void startDrag( const KCalCore::Incidence::Ptr & ); void startDrag( const Akonadi::Item & ); void readSettings(); void readSettings( const KConfig * ); void writeSettings( KConfig * ); /** reschedule the todo to the given x- and y- coordinates. Third parameter determines all-day (no time specified) */ void slotIncidencesDropped( const KCalCore::Incidence::List &incidences, const QPoint &, bool ); void slotIncidencesDropped( const QList& incidences, const QPoint &, bool ); void enableAgendaUpdate( bool enable ); void setIncidenceChanger( Akonadi::IncidenceChanger *changer ); void zoomInHorizontally( const QDate &date=QDate() ); void zoomOutHorizontally( const QDate &date=QDate() ); void zoomInVertically( ); void zoomOutVertically( ); void zoomView( const int delta, const QPoint &pos, const Qt::Orientation orient=Qt::Horizontal ); void clearTimeSpanSelection(); // Used by the timelabelszone void updateTimeBarWidth(); /** Create labels for the selected dates. */ void createDayLabels( bool force ); void createTimeBarHeaders(); void setChanges( EventView::Changes ); Q_SIGNALS: void showNewEventPopupSignal(); void showIncidencePopupSignal( Akonadi::Item, QDate ); void zoomViewHorizontally( const QDate &, int count ); void timeSpanSelectionChanged(); protected: /** Fill agenda using the current set value for the start date */ void fillAgenda(); void connectAgenda( Agenda *agenda, Agenda *otherAgenda ); /** Set the masks on the agenda widgets indicating, which days are holidays. */ void setHolidayMasks(); void removeIncidence( const KCalCore::Incidence::Ptr &inc ); virtual void resizeEvent( QResizeEvent *resizeEvent ); protected Q_SLOTS: void updateEventIndicatorTop( int newY ); void updateEventIndicatorBottom( int newY ); /** Updates data for selected timespan */ void newTimeSpanSelected( const QPoint &start, const QPoint &end ); /** Updates data for selected timespan for all day event*/ void newTimeSpanSelectedAllDay( const QPoint &start, const QPoint &end ); /** Updates the event indicators after a certain incidence was modified or removed. */ void updateEventIndicators(); void scheduleUpdateEventIndicators(); void updateDayLabelSizes(); void alignAgendas(); private slots: void slotIncidenceSelected(const KCalCore::Incidence::Ptr &incidence, QDate date); void slotShowIncidencePopup(const KCalCore::Incidence::Ptr &incidence, QDate date); void slotEditIncidence(const KCalCore::Incidence::Ptr &incidence, const KDateTime &occurrenceDate); void slotShowIncidence(const KCalCore::Incidence::Ptr &incidence); void slotDeleteIncidence(const KCalCore::Incidence::Ptr &incidence); private: void init( const QDate &start, const QDate &end ); bool filterByCollectionSelection( const KCalCore::Incidence::Ptr &incidence ); void setupTimeLabel( TimeLabels *timeLabel ); bool displayIncidence( const KCalCore::Incidence::Ptr &incidence, bool createSelected ); #ifndef EVENTVIEWS_NODECOS typedef QList DecorationList; bool loadDecorations( const QStringList &decorations, DecorationList &decoList ); void placeDecorationsFrame( KHBox *frame, bool decorationsFound, bool isTop ); void placeDecorations( DecorationList &decoList, const QDate &date, KHBox *labelBox, bool forWeek ); #endif friend class TimeLabelsZone; friend class MultiAgendaView; Agenda *agenda() const; Agenda *allDayAgenda() const; private: class Private; Private *const d; }; } #endif // kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/calendarviews/eventview.cpp b/calendarviews/eventview.cpp index f41e251eaa..92b4c3963d 100644 --- a/calendarviews/eventview.cpp +++ b/calendarviews/eventview.cpp @@ -1,680 +1,704 @@ /* Copyright (c) 2000,2001 Cornelius Schumacher Copyright (C) 2003-2004 Reinhold Kainhofer Copyright (C) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net Author: Kevin Krammer, krake@kdab.com Author: Sergio Martins, sergio.martins@kdab.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #include "eventview_p.h" #include "prefs.h" #include #include #include #include #include using namespace Future; -#include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace KCalCore; using namespace EventViews; using namespace Akonadi; static const KCatalogLoader loaderCatalog( QLatin1String("libeventviews") ); CalendarSupport::CollectionSelection *EventViewPrivate::sGlobalCollectionSelection = 0; /* static */ void EventView::setGlobalCollectionSelection( CalendarSupport::CollectionSelection *s ) { EventViewPrivate::sGlobalCollectionSelection = s; } EventView::EventView( QWidget *parent ) : QWidget( parent ), d_ptr( new EventViewPrivate() ) { + d_ptr->calendar->mEventView = this; + QByteArray cname = metaObject()->className(); cname.replace( ':', '_' ); d_ptr->identifier = cname + '_' + KRandom::randomString( 8 ).toLatin1(); //AKONADI_PORT review: the FocusLineEdit in the editor emits focusReceivedSignal(), //which triggered finishTypeAhead. But the global focus widget in QApplication is //changed later, thus subsequent keyevents still went to this view, triggering another //editor, for each keypress. //Thus, listen to the global focusChanged() signal (seen in Qt 4.6-stable-patched 20091112 -Frank) connect( qobject_cast( QApplication::instance() ), SIGNAL(focusChanged(QWidget*,QWidget*)), this, SLOT(focusChanged(QWidget*,QWidget*)) ); d_ptr->setUpModels(); } EventView::~EventView() { delete d_ptr; } void EventView::defaultAction( const Akonadi::Item &aitem ) { kDebug(); const Incidence::Ptr incidence = CalendarSupport::incidence( aitem ); if ( !incidence ) { return; } kDebug() << " type:" << int( incidence->type() ); if ( incidence->isReadOnly() ) { emit showIncidenceSignal(aitem); } else { //TODO find occurrence date emit editIncidenceSignal(aitem, KDateTime()); } } void EventView::setHolidayRegion( const KHolidays::HolidayRegionPtr &holidayRegion ) { Q_D( EventView ); d->mHolidayRegion = holidayRegion; } int EventView::showMoveRecurDialog( const Incidence::Ptr &inc, const QDate &date ) { KDateTime dateTime( date, preferences()->timeSpec() ); int availableOccurrences = KCalUtils::RecurrenceActions::availableOccurrences( inc, dateTime ); const QString caption = i18nc( "@title:window", "Changing Recurring Item" ); KGuiItem itemFuture( i18n( "Also &Future Items" ) ); KGuiItem itemSelected( i18n( "Only &This Item" ) ); KGuiItem itemAll( i18n( "&All Occurrences" ) ); switch ( availableOccurrences ) { case KCalUtils::RecurrenceActions::NoOccurrence: return KCalUtils::RecurrenceActions::NoOccurrence; case KCalUtils::RecurrenceActions::SelectedOccurrence: return KCalUtils::RecurrenceActions::SelectedOccurrence; case KCalUtils::RecurrenceActions::AllOccurrences: { Q_ASSERT( availableOccurrences & KCalUtils::RecurrenceActions::SelectedOccurrence ); // if there are all kinds of ooccurrences (i.e. past present and future) the user might // want the option only apply to current and future occurrences, leaving the past ones // provide a third choice for that ("Also future") if ( availableOccurrences == KCalUtils::RecurrenceActions::AllOccurrences ) { const QString message = i18n( "The item you are trying to change is a recurring item. " "Should the changes be applied only to this single occurrence, " "also to future items, or to all items in the recurrence?" ); return KCalUtils::RecurrenceActions::questionSelectedFutureAllCancel( message, caption, itemSelected, itemFuture, itemAll, this ); } } default: Q_ASSERT( availableOccurrences & KCalUtils::RecurrenceActions::SelectedOccurrence ); // selected occurrence and either past or future occurrences const QString message = i18n( "The item you are trying to change is a recurring item. " "Should the changes be applied only to this single occurrence " "or to all items in the recurrence?" ); return KCalUtils::RecurrenceActions::questionSelectedAllCancel( message, caption, itemSelected, itemAll, this ); break; } return KCalUtils::RecurrenceActions::NoOccurrence; } void EventView::setCalendar( const Akonadi::ETMCalendar::Ptr &calendar ) { Q_D( EventView ); - if ( d->calendar != calendar ) { - if (d->calendar) - disconnect(d->calendar.data()); + if ( d->calendar->etmCalendar() != calendar ) { + if (d->calendar->etmCalendar()) + disconnect(d->calendar->etmCalendar().data()); - d->calendar = calendar; + d->calendar->setETMCalendar(calendar); if ( calendar ) { if ( d->collectionSelectionModel ) d->collectionSelectionModel->setSourceModel( calendar->model() ); connect( calendar.data(), SIGNAL(collectionChanged(Akonadi::Collection,QSet)), SLOT(onCollectionChanged(Akonadi::Collection,QSet)) ); } } } -Akonadi::ETMCalendar::Ptr EventView::calendar() const +void EventView::setCalendar(const MultiViewCalendar::Ptr &calendar) +{ + Q_D( EventView ); + if (d->calendar != calendar) { + Akonadi::ETMCalendar::Ptr etmOld = etmCalendar(); + + d->calendar = calendar; + + if(calendar->etmCalendar() != etmOld) { + Akonadi::ETMCalendar::Ptr etmNew = calendar->etmCalendar(); + calendar->setETMCalendar(etmOld); + setCalendar(etmNew); + } + } +} + +KCalCore::Calendar::Ptr EventView::calendar(const KCalCore::Incidence::Ptr incidence) const +{ + Q_D( const EventView ); + return d->calendar->findCalendar(incidence)->getCalendar(); +} + +ETMCalendar::Ptr EventView::calendar() const +{ + return etmCalendar(); +} + +MultiViewCalendar::Ptr EventView::viewCalendar() const { Q_D( const EventView ); return d->calendar; } +ETMCalendar::Ptr EventView::etmCalendar() const +{ + Q_D( const EventView ); + return d->calendar->etmCalendar(); +} + +int EventView::calendars() const +{ + Q_D( const EventView ); + return d->calendar->calendars(); +} + void EventView::setPreferences( const PrefsPtr &preferences ) { Q_D( EventView ); if ( d->mPrefs != preferences ) { if ( preferences ) { d->mPrefs = preferences; } else { d->mPrefs = PrefsPtr( new Prefs() ); } updateConfig(); } } void EventView::setKCalPreferences( const KCalPrefsPtr &preferences ) { Q_D( EventView ); if ( d->mKCalPrefs != preferences ) { if ( preferences ) { d->mKCalPrefs = preferences; } else { d->mKCalPrefs = KCalPrefsPtr( new CalendarSupport::KCalPrefs() ); } updateConfig(); } } PrefsPtr EventView::preferences() const { Q_D( const EventView ); return d->mPrefs; } KCalPrefsPtr EventView::kcalPreferences() const { Q_D( const EventView ); return d->mKCalPrefs; } void EventView::dayPassed( const QDate & ) { updateView(); } void EventView::setIncidenceChanger( Akonadi::IncidenceChanger *changer ) { Q_D( EventView ); d->mChanger = changer; } void EventView::flushView() { } EventView *EventView::viewAt( const QPoint & ) { return this; } void EventView::updateConfig() { } QDateTime EventView::selectionStart() const { return QDateTime(); } QDateTime EventView::selectionEnd() const { return QDateTime(); } bool EventView::dateRangeSelectionEnabled() const { Q_D( const EventView ); return d->mDateRangeSelectionEnabled; } void EventView::setDateRangeSelectionEnabled( bool enable ) { Q_D( EventView ); d->mDateRangeSelectionEnabled = enable; } bool EventView::supportsZoom() const { return false; } bool EventView::hasConfigurationDialog() const { return false; } void EventView::setDateRange( const KDateTime &start, const KDateTime &end, const QDate &preferredMonth ) { Q_D( EventView ); d->startDateTime = start; d->endDateTime = end; showDates( start.date(), end.date(), preferredMonth ); const QPair adjusted = actualDateRange( start, end, preferredMonth ); d->actualStartDateTime = adjusted.first; d->actualEndDateTime = adjusted.second; } KDateTime EventView::startDateTime() const { Q_D( const EventView ); return d->startDateTime; } KDateTime EventView::endDateTime() const { Q_D( const EventView ); return d->endDateTime; } KDateTime EventView::actualStartDateTime() const { Q_D( const EventView ); return d->actualStartDateTime; } KDateTime EventView::actualEndDateTime() const { Q_D( const EventView ); return d->actualEndDateTime; } void EventView::showConfigurationDialog( QWidget * ) { } bool EventView::processKeyEvent( QKeyEvent *ke ) { Q_D( EventView ); // If Return is pressed bring up an editor for the current selected time span. if ( ke->key() == Qt::Key_Return ) { if ( ke->type() == QEvent::KeyPress ) { d->mReturnPressed = true; } else if ( ke->type() == QEvent::KeyRelease ) { if ( d->mReturnPressed ) { emit newEventSignal(); d->mReturnPressed = false; return true; } else { d->mReturnPressed = false; } } } // Ignore all input that does not produce any output if ( ke->text().isEmpty() || ( ke->modifiers() & Qt::ControlModifier ) ) { return false; } if ( ke->type() == QEvent::KeyPress ) { switch ( ke->key() ) { case Qt::Key_Escape: case Qt::Key_Return: case Qt::Key_Enter: case Qt::Key_Tab: case Qt::Key_Backtab: case Qt::Key_Left: case Qt::Key_Right: case Qt::Key_Up: case Qt::Key_Down: case Qt::Key_Backspace: case Qt::Key_Delete: case Qt::Key_PageUp: case Qt::Key_PageDown: case Qt::Key_Home: case Qt::Key_End: case Qt::Key_Control: case Qt::Key_Meta: case Qt::Key_Alt: break; default: d->mTypeAheadEvents.append( new QKeyEvent( ke->type(), ke->key(), ke->modifiers(), ke->text(), ke->isAutoRepeat(), static_cast( ke->count() ) ) ); if ( !d->mTypeAhead ) { d->mTypeAhead = true; emit newEventSignal(); } return true; } } return false; } void EventView::setTypeAheadReceiver( QObject *o ) { Q_D( EventView ); d->mTypeAheadReceiver = o; } void EventView::focusChanged( QWidget *, QWidget *now ) { Q_D( EventView ); if ( d->mTypeAhead && now && now == d->mTypeAheadReceiver ) { d->finishTypeAhead(); } } CalendarSupport::CollectionSelection *EventView::collectionSelection() const { Q_D( const EventView ); return d->customCollectionSelection ? d->customCollectionSelection : globalCollectionSelection(); } void EventView::setCustomCollectionSelectionProxyModel( KCheckableProxyModel *model ) { Q_D( EventView ); if ( d->collectionSelectionModel == model ) { return; } delete d->collectionSelectionModel; d->collectionSelectionModel = model; d->setUpModels(); } KCheckableProxyModel *EventView::customCollectionSelectionProxyModel() const { Q_D( const EventView ); return d->collectionSelectionModel; } KCheckableProxyModel *EventView::takeCustomCollectionSelectionProxyModel() { Q_D( EventView ); KCheckableProxyModel *m = d->collectionSelectionModel; d->collectionSelectionModel = 0; d->setUpModels(); return m; } CalendarSupport::CollectionSelection *EventView::customCollectionSelection() const { Q_D( const EventView ); return d->customCollectionSelection; } void EventView::clearSelection() { } bool EventView::eventDurationHint( QDateTime &startDt, QDateTime &endDt, bool &allDay ) const { Q_UNUSED( startDt ); Q_UNUSED( endDt ); Q_UNUSED( allDay ); return false; } Akonadi::IncidenceChanger *EventView::changer() const { Q_D( const EventView ); return d->mChanger; } void EventView::doRestoreConfig( const KConfigGroup & ) { } void EventView::doSaveConfig( KConfigGroup & ) { } QPair EventView::actualDateRange( const KDateTime &start, const KDateTime &end, const QDate &preferredMonth ) const { Q_UNUSED( preferredMonth ); return qMakePair( start, end ); } /* void EventView::incidencesAdded( const Akonadi::Item::List & ) { } void EventView::incidencesAboutToBeRemoved( const Akonadi::Item::List & ) { } void EventView::incidencesChanged( const Akonadi::Item::List & ) { } */ void EventView::handleBackendError( const QString &errorString ) { kError() << errorString; } void EventView::calendarReset() { } CalendarSupport::CollectionSelection *EventView::globalCollectionSelection() { return EventViewPrivate::sGlobalCollectionSelection; } QByteArray EventView::identifier() const { Q_D( const EventView ); return d->identifier; } void EventView::setIdentifier( const QByteArray &identifier ) { Q_D( EventView ); d->identifier = identifier; } void EventView::setChanges( Changes changes ) { Q_D( EventView ); if ( d->mChanges == NothingChanged ) { QMetaObject::invokeMethod( this, "updateView", Qt::QueuedConnection ); } d->mChanges = changes; } EventView::Changes EventView::changes() const { Q_D( const EventView ); return d->mChanges; } void EventView::restoreConfig( const KConfigGroup &configGroup ) { Q_D( EventView ); const bool useCustom = configGroup.readEntry( "UseCustomCollectionSelection", false ); if ( !d->collectionSelectionModel && !useCustom ) { delete d->collectionSelectionModel; d->collectionSelectionModel = 0; d->setUpModels(); } else if ( useCustom ) { if ( !d->collectionSelectionModel ) { // Sort the calendar model on calendar name QSortFilterProxyModel *sortProxy = new QSortFilterProxyModel( this ); sortProxy->setDynamicSortFilter( true ); sortProxy->setSortCaseSensitivity( Qt::CaseInsensitive ); if ( d->calendar ) { - sortProxy->setSourceModel( d->calendar->entityTreeModel() ); + sortProxy->setSourceModel( d->calendar->etmCalendar()->entityTreeModel() ); } // Only show the first column. KColumnFilterProxyModel *columnFilterProxy = new KColumnFilterProxyModel( this ); columnFilterProxy->setVisibleColumn( Akonadi::ETMCalendar::CollectionTitle ); columnFilterProxy->setSourceModel( sortProxy ); // Make the calendar model checkable. d->collectionSelectionModel = new KCheckableProxyModel( this ); d->collectionSelectionModel->setSourceModel( columnFilterProxy ); d->setUpModels(); } const KConfigGroup selectionGroup = configGroup.config()->group( configGroup.name() + QLatin1String( "_selectionSetup" ) ); KViewStateMaintainer maintainer( selectionGroup ); maintainer.setSelectionModel( d->collectionSelectionModel->selectionModel() ); maintainer.restoreState(); } doRestoreConfig( configGroup ); } void EventView::saveConfig( KConfigGroup &configGroup ) { Q_D( EventView ); configGroup.writeEntry( "UseCustomCollectionSelection", d->collectionSelectionModel != 0 ); if ( d->collectionSelectionModel ) { KConfigGroup selectionGroup = configGroup.config()->group( configGroup.name() + QLatin1String( "_selectionSetup" ) ); KViewStateMaintainer maintainer( selectionGroup ); maintainer.setSelectionModel( d->collectionSelectionModel->selectionModel() ); maintainer.saveState(); } doSaveConfig( configGroup ); } void EventView::setCollectionId( Akonadi::Collection::Id id ) { Q_D( EventView ); if ( d->mCollectionId != id ) { d->mCollectionId = id; } } Akonadi::Collection::Id EventView::collectionId() const { Q_D( const EventView ); return d->mCollectionId; } bool EventView::makesWholeDayBusy( const KCalCore::Incidence::Ptr &incidence ) const { // Must be event // Must be all day // Must be marked busy (TRANSP: OPAQUE) // You must be attendee or organizer if ( incidence->type() != KCalCore::Incidence::TypeEvent || !incidence->allDay() ) { return false; } KCalCore::Event::Ptr ev = incidence.staticCast(); if ( ev->transparency() != KCalCore::Event::Opaque ) { return false; } // Last check: must be organizer or attendee: if ( kcalPreferences()->thatIsMe( ev->organizer()->email() ) ) { return true; } KCalCore::Attendee::List attendees = ev->attendees(); KCalCore::Attendee::List::ConstIterator it; for ( it = attendees.constBegin(); it != attendees.constEnd(); ++it ) { if ( kcalPreferences()->thatIsMe( (*it)->email() ) ) { return true; } } return false; } /*static*/ QColor EventView::itemFrameColor( const QColor &color, bool selected ) { if ( color.isValid() ) { return selected ? QColor( 85 + color.red() * 2.0 / 3, 85 + color.green() * 2.0 / 3, 85 + color.blue() * 2.0 / 3 ) : color.dark( 115 ); } else { return Qt::black; } } -QString EventView::iconForItem( const Akonadi::Item &item ) -{ - QString iconName; - Akonadi::Collection collection = item.parentCollection(); - while ( collection.parentCollection().isValid() && - collection.parentCollection() != Akonadi::Collection::root() ) { - collection = calendar()->collection( collection.parentCollection().id() ); - } - - if ( collection.isValid() && collection.hasAttribute() ) { - iconName = collection.attribute()->iconName(); - } - - return iconName; -} - void EventView::onCollectionChanged(const Akonadi::Collection &collection, const QSet &changedAttributes) { Q_UNUSED(collection); if (changedAttributes.contains("AccessRights")) { setChanges(changes() | EventViews::EventView::ResourcesChanged); updateView(); } } // kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/calendarviews/eventview.h b/calendarviews/eventview.h index dece4ce0c6..2bc9af2ade 100644 --- a/calendarviews/eventview.h +++ b/calendarviews/eventview.h @@ -1,533 +1,542 @@ /* Copyright (c) 1999 Preston Brown Copyright (c) 2000,2001 Cornelius Schumacher Copyright (C) 2003-2004 Reinhold Kainhofer Copyright (C) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net Author: Kevin Krammer, krake@kdab.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #ifndef EVENTVIEWS_EVENTVIEW_H #define EVENTVIEWS_EVENTVIEW_H #include "eventviews_export.h" #include #include -#include +#include "viewcalendar.h" #include #include #include #include #include namespace boost { template class shared_ptr; } namespace KCalCore { template class SortableList; typedef SortableList DateList; } namespace KHolidays { class HolidayRegion; typedef boost::shared_ptr HolidayRegionPtr; } namespace CalendarSupport { class CollectionSelection; class KCalPrefs; } namespace Akonadi { class IncidenceChanger; } class KCheckableProxyModel; class KConfigGroup; namespace EventViews { enum { BUSY_BACKGROUND_ALPHA = 70 }; class EventViewPrivate; class Prefs; typedef boost::shared_ptr PrefsPtr; typedef boost::shared_ptr KCalPrefsPtr; /** EventView is the abstract base class from which all other calendar views for event data are derived. It provides methods for displaying appointments and events on one or more days. The actual number of days that a view actually supports is not defined by this abstract class; that is up to the classes that inherit from it. It also provides methods for updating the display, retrieving the currently selected event (or events), and the like. @short Abstract class from which all event views are derived. @author Preston Brown @see KOListView, AgendaView, KOMonthView */ class EVENTVIEWS_EXPORT EventView : public QWidget { Q_OBJECT public: enum { // This value is passed to QColor's lighter(int factor) for selected events BRIGHTNESS_FACTOR = 125 }; enum ItemIcon { CalendarCustomIcon = 0, TaskIcon, JournalIcon, RecurringIcon, ReminderIcon, ReadOnlyIcon, ReplyIcon, AttendingIcon, TentativeIcon, OrganizerIcon, IconCount = 10 // Always keep at the end }; enum Change { NothingChanged = 0, IncidencesAdded = 1, IncidencesEdited = 2, IncidencesDeleted = 4, DatesChanged = 8, FilterChanged = 16, ResourcesChanged = 32, ZoomChanged = 64, ConfigChanged = 128 }; Q_DECLARE_FLAGS( Changes, Change ) /** * Constructs a view. * @param cal is a pointer to the calendar object from which events * will be retrieved for display. * @param parent is the parent QWidget. */ explicit EventView( QWidget *parent = 0 ); /** * Destructor. Views will do view-specific cleanups here. */ ~EventView(); virtual void setCalendar( const Akonadi::ETMCalendar::Ptr &cal ); + virtual void setCalendar( const MultiViewCalendar::Ptr &cal ); /** Return calendar object of this view. - TODO: replace with a version that returns a KCalcCore::Calendar to be able to use it in different enviroments - see agendaview for example calendar2(incidence) */ - virtual Akonadi::ETMCalendar::Ptr calendar() const; + virtual KCalCore::Calendar::Ptr calendar(const KCalCore::Incidence::Ptr incidence) const; + /** + * Normally the Views should use either calendar(incidence) to get the calendar for the incidence + * if something really relies on the etm they should use etmCalendar() instead + */ + + virtual EVENTVIEWS_EXPORT_DEPRECATED Akonadi::ETMCalendar::Ptr calendar() const; + + virtual Akonadi::ETMCalendar::Ptr etmCalendar() const; + + virtual MultiViewCalendar::Ptr viewCalendar() const; + + virtual int calendars() const; /* update config is called after prefs are set. */ virtual void setPreferences( const PrefsPtr &preferences ); PrefsPtr preferences() const; virtual void setKCalPreferences( const KCalPrefsPtr &preferences ); KCalPrefsPtr kcalPreferences() const; /** @return a list of selected events. Most views can probably only select a single event at a time, but some may be able to select more than one. */ virtual Akonadi::Item::List selectedIncidences() const = 0; /** Returns a list of the dates of selected events. Most views can probably only select a single event at a time, but some may be able to select more than one. */ virtual KCalCore::DateList selectedIncidenceDates() const = 0; /** Returns the start of the selection, or an invalid QDateTime if there is no selection or the view doesn't support selecting cells. */ virtual QDateTime selectionStart() const; /** Returns the end of the selection, or an invalid QDateTime if there is no selection or the view doesn't support selecting cells. */ virtual QDateTime selectionEnd() const; /** Returns whether or not date range selection is enabled. This setting only applies to views that actually supports selecting cells. @see selectionStart() @see selectionEnd() */ bool dateRangeSelectionEnabled() const; /** Enable or disable date range selection. @see dateRangeSelectionEnabled() */ void setDateRangeSelectionEnabled( bool enable ); /** Returns the number of currently shown dates. A return value of 0 means no idea. */ virtual int currentDateCount() const = 0; /** * returns whether this view supports zoom. * Base implementation returns false. */ virtual bool supportsZoom() const; virtual bool hasConfigurationDialog() const; virtual void showConfigurationDialog( QWidget *parent ); QByteArray identifier() const; void setIdentifier( const QByteArray &identifier ); /** * reads the view configuration. View-specific configuration can be * restored via doRestoreConfig() * * @param configGroup the group to read settings from * @see doRestoreConfig() */ void restoreConfig( const KConfigGroup &configGroup ); /** * writes out the view configuration. View-specific configuration can be * saved via doSaveConfig() * * @param configGroup the group to store settings in * @see doSaveConfig() */ void saveConfig( KConfigGroup &configGroup ); /** Makes the eventview display only items of collection @p id. Useful for example in multi-agendaview (side-by-side) where each AgendaView displays only one collection. */ void setCollectionId( Akonadi::Collection::Id id ); Akonadi::Collection::Id collectionId() const; //---------------------------------------------------------------------------- KCheckableProxyModel *takeCustomCollectionSelectionProxyModel(); KCheckableProxyModel *customCollectionSelectionProxyModel() const; void setCustomCollectionSelectionProxyModel( KCheckableProxyModel *model ); CalendarSupport::CollectionSelection *customCollectionSelection() const; static CalendarSupport::CollectionSelection *globalCollectionSelection(); static void setGlobalCollectionSelection( CalendarSupport::CollectionSelection *selection ); //---------------------------------------------------------------------------- /** * returns the view at the given widget coordinate. This is usually the view * itself, except for composite views, where a subview will be returned. * The default implementation returns @p this . */ virtual EventView *viewAt( const QPoint &p ); /** * @param preferredMonth Used by month orientated views. Contains the * month to show when the week crosses months. It's a QDate instead * of uint so it can be easily fed to KCalendarSystem's functions. */ virtual void setDateRange( const KDateTime &start, const KDateTime &end, const QDate &preferredMonth = QDate() ); KDateTime startDateTime() const; KDateTime endDateTime() const; KDateTime actualStartDateTime() const; KDateTime actualEndDateTime() const; int showMoveRecurDialog( const KCalCore::Incidence::Ptr &incidence, const QDate &date ); /** Handles key events, opens the new event dialog when enter is pressed, activates type ahead. */ bool processKeyEvent( QKeyEvent * ); /* * Sets the QObject that will receive key events that were made * while the new event dialog was still being created. */ void setTypeAheadReceiver( QObject *o ); /** Returns the selection of collection to be used by this view (custom if set, or global otherwise). */ CalendarSupport::CollectionSelection *collectionSelection() const; /** Notifies the view that there are pending changes so a redraw is needed. @param needed if the update is needed or not. */ virtual void setChanges( Changes changes ); /** Returns if there are pending changes and a redraw is needed. */ Changes changes() const; /** * Returns a variation of @p color that will be used for the border * of an agenda or month item. */ static QColor itemFrameColor( const QColor &color, bool selected ); - QString iconForItem( const Akonadi::Item &); - public Q_SLOTS: /** Shows given incidences. Depending on the actual view it might not be possible to show all given events. @param incidenceList a list of incidences to show. @param date is the QDate on which the incidences are being shown. */ virtual void showIncidences( const Akonadi::Item::List &incidenceList, const QDate &date ) = 0; /** Updates the current display to reflect changes that may have happened in the calendar since the last display refresh. */ virtual void updateView() = 0; virtual void dayPassed( const QDate & ); /** Assign a new incidence change helper object. */ virtual void setIncidenceChanger( Akonadi::IncidenceChanger *changer ); /** Write all unsaved data back to calendar store. */ virtual void flushView(); /** Re-reads the configuration and picks up relevant changes which are applicable to the view. */ virtual void updateConfig(); /** Clear selection. The incidenceSelected signal is not emitted. */ virtual void clearSelection(); /** Sets the default start/end date/time for new events. Return true if anything was changed */ virtual bool eventDurationHint( QDateTime &startDt, QDateTime &endDt, bool &allDay ) const; void focusChanged( QWidget *, QWidget * ); /** Perform the default action for an incidence, e.g. open the event editor, when double-clicking an event in the agenda view. */ void defaultAction( const Akonadi::Item &incidence ); /** Set which holidays the user wants to use. @param holidayRegion a HolidayRegion object initialized with the desired locale. */ void setHolidayRegion( const KHolidays::HolidayRegionPtr &holidayRegion ); Q_SIGNALS: /** * when the view changes the dates that are selected in one way or * another, this signal is emitted. It should be connected back to * the KDateNavigator object so that it changes appropriately, * and any other objects that need to be aware that the list of * selected dates has changed. * @param datelist the new list of selected dates */ void datesSelected( const KCalCore::DateList &datelist ); /** * Emitted when an event is moved using the mouse in an agenda * view (week / month). */ void shiftedEvent( const QDate &olddate, const QDate &newdate ); void incidenceSelected( const Akonadi::Item &, const QDate ); /** * instructs the receiver to show the incidence in read-only mode. */ void showIncidenceSignal( const Akonadi::Item & ); /** * instructs the receiver to begin editing the incidence specified in * some manner. Doesn't make sense to connect to more than one * receiver. */ void editIncidenceSignal( const Akonadi::Item &, const KDateTime &occurrenceDate ); /** * instructs the receiver to delete the Incidence in some manner; some * possibilities include automatically, with a confirmation dialog * box, etc. Doesn't make sense to connect to more than one receiver. */ void deleteIncidenceSignal( const Akonadi::Item & ); /** * instructs the receiver to cut the Incidence */ void cutIncidenceSignal( const Akonadi::Item & ); /** * instructs the receiver to copy the incidence */ void copyIncidenceSignal( const Akonadi::Item & ); /** * instructs the receiver to paste the incidence */ void pasteIncidenceSignal(); /** * instructs the receiver to toggle the alarms of the Incidence. */ void toggleAlarmSignal( const Akonadi::Item & ); /** * instructs the receiver to toggle the completion state of the Incidence * (which must be a Todo type). */ void toggleTodoCompletedSignal( const Akonadi::Item & ); /** * Copy the incidence to the specified resource. */ void copyIncidenceToResourceSignal( const Akonadi::Item &, const QString & ); /** * Move the incidence to the specified resource. */ void moveIncidenceToResourceSignal( const Akonadi::Item &, const QString & ); /** Dissociate from a recurring incidence the occurrence on the given * date to a new incidence or dissociate all occurrences from the * given date onwards. */ void dissociateOccurrencesSignal( const Akonadi::Item &, const QDate & ); /** * instructs the receiver to create a new event in given collection. Doesn't make * sense to connect to more than one receiver. */ void newEventSignal(); /** * instructs the receiver to create a new event with the specified beginning * time. Doesn't make sense to connect to more than one receiver. */ void newEventSignal( const QDate & ); /** * instructs the receiver to create a new event with the specified beginning * time. Doesn't make sense to connect to more than one receiver. */ void newEventSignal( const QDateTime & ); /** * instructs the receiver to create a new event, with the specified * beginning end ending times. Doesn't make sense to connect to more * than one receiver. */ void newEventSignal( const QDateTime &, const QDateTime & ); void newTodoSignal( const QDate & ); void newSubTodoSignal( const Akonadi::Item & ); void newJournalSignal( const QDate & ); protected Q_SLOTS: virtual void calendarReset(); private Q_SLOTS: void onCollectionChanged(const Akonadi::Collection &, const QSet &); protected: bool makesWholeDayBusy( const KCalCore::Incidence::Ptr &incidence ) const; Akonadi::IncidenceChanger *changer() const; /** * reimplement to read view-specific settings. */ virtual void doRestoreConfig( const KConfigGroup &configGroup ); /** * reimplement to write view-specific settings. */ virtual void doSaveConfig( KConfigGroup &configGroup ); /** @deprecated */ virtual void showDates( const QDate &start, const QDate &end, const QDate &preferredMonth = QDate() ) = 0; /** * from the requested date range (passed via setDateRange()), calculates the * adjusted date range actually displayed by the view, depending on the * view's supported range (e.g., a month view always displays one month) * The default implementation returns the range unmodified * * @param preferredMonth Used by month orientated views. Contains the * month to show when the week crosses months. It's a QDate instead of * uint so it can be easily fed to KCalendarSystem's functions. */ virtual QPair actualDateRange( const KDateTime &start, const KDateTime &end, const QDate &preferredMonth = QDate() ) const; /* virtual void incidencesAdded( const Akonadi::Item::List &incidences ); virtual void incidencesAboutToBeRemoved( const Akonadi::Item::List &incidences ); virtual void incidencesChanged( const Akonadi::Item::List &incidences ); */ virtual void handleBackendError( const QString &error ); private: EventViewPrivate * const d_ptr; Q_DECLARE_PRIVATE( EventView ) }; } #endif // kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/calendarviews/eventview_p.cpp b/calendarviews/eventview_p.cpp index 796cff96c7..a545456cc9 100644 --- a/calendarviews/eventview_p.cpp +++ b/calendarviews/eventview_p.cpp @@ -1,78 +1,78 @@ /* Copyright (c) 2000,2001 Cornelius Schumacher Copyright (C) 2003-2004 Reinhold Kainhofer Copyright (C) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net Author: Kevin Krammer, krake@kdab.com Author: Sergio Martins, sergio.martins@kdab.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #include "eventview_p.h" #include "prefs.h" #include #include #include #include using namespace EventViews; EventViewPrivate::EventViewPrivate() - : calendar( 0 ), + : calendar( MultiViewCalendar::Ptr( new MultiViewCalendar()) ), customCollectionSelection( 0 ), collectionSelectionModel( 0 ), mReturnPressed( false ), mDateRangeSelectionEnabled( true ), mTypeAhead( false ), mTypeAheadReceiver( 0 ), mPrefs( new Prefs() ), mKCalPrefs( new CalendarSupport::KCalPrefs() ), mChanger( 0 ), mChanges( EventView::DatesChanged ), mCollectionId( -1 ) { } EventViewPrivate::~EventViewPrivate() { delete collectionSelectionModel; } void EventViewPrivate::finishTypeAhead() { if ( mTypeAheadReceiver ) { foreach ( QEvent *e, mTypeAheadEvents ) { QApplication::sendEvent( mTypeAheadReceiver, e ); } } qDeleteAll( mTypeAheadEvents ); mTypeAheadEvents.clear(); mTypeAhead = false; } void EventViewPrivate::setUpModels() { delete customCollectionSelection; customCollectionSelection = 0; if ( collectionSelectionModel ) { customCollectionSelection = new CalendarSupport::CollectionSelection( collectionSelectionModel->selectionModel() ); } } diff --git a/calendarviews/eventview_p.h b/calendarviews/eventview_p.h index 889ceec58a..b759e7b1b8 100644 --- a/calendarviews/eventview_p.h +++ b/calendarviews/eventview_p.h @@ -1,85 +1,85 @@ /* Copyright (c) 2000,2001 Cornelius Schumacher Copyright (C) 2003-2004 Reinhold Kainhofer Copyright (C) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net Author: Kevin Krammer, krake@kdab.com Author: Sergio Martins, sergio.martins@kdab.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #ifndef EVENTVIEWS_EVENTVIEW_P_H #define EVENTVIEWS_EVENTVIEW_P_H #include "eventview.h" #include class KCheckableProxyModel; namespace EventViews { class EventViewPrivate { public: /// Methods EventViewPrivate(); ~EventViewPrivate(); /** This is called when the new event dialog is shown. It sends all events in mTypeAheadEvents to the receiver. */ void finishTypeAhead(); public: // virtual functions void setUpModels(); public: /// Members - Akonadi::ETMCalendar::Ptr calendar; + MultiViewCalendar::Ptr calendar; CalendarSupport::CollectionSelection *customCollectionSelection; KCheckableProxyModel *collectionSelectionModel; QByteArray identifier; KDateTime startDateTime; KDateTime endDateTime; KDateTime actualStartDateTime; KDateTime actualEndDateTime; /* When we receive a QEvent with a key_Return release * we will only show a new event dialog if we previously received a * key_Return press, otherwise a new event dialog appears when * you hit return in some yes/no dialog */ bool mReturnPressed; bool mDateRangeSelectionEnabled; bool mTypeAhead; QObject *mTypeAheadReceiver; QList mTypeAheadEvents; static CalendarSupport::CollectionSelection *sGlobalCollectionSelection; KHolidays::HolidayRegionPtr mHolidayRegion; PrefsPtr mPrefs; KCalPrefsPtr mKCalPrefs; Akonadi::IncidenceChanger *mChanger; EventView::Changes mChanges; Akonadi::Collection::Id mCollectionId; }; } // EventViews #endif diff --git a/calendarviews/journal/journalframe.cpp b/calendarviews/journal/journalframe.cpp index bbd24c921b..72e9523608 100644 --- a/calendarviews/journal/journalframe.cpp +++ b/calendarviews/journal/journalframe.cpp @@ -1,321 +1,321 @@ /* This file is part of KOrganizer. Copyright (c) 2001 Cornelius Schumacher Copyright (C) 2003-2004 Reinhold Kainhofer Copyright (c) 2007 Mike McQuaid This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ // Journal Entry #include "journalframe.h" #include #include #include #include #include #include #include #include #include #include #include using namespace EventViews; -JournalDateView::JournalDateView( const Akonadi::ETMCalendar::Ptr &calendar, QWidget *parent ) +JournalDateView::JournalDateView( const MultiViewCalendar::Ptr &calendar, QWidget *parent ) : KVBox( parent ), mCalendar( calendar ), mChanger( 0 ) { } JournalDateView::~JournalDateView() { } void JournalDateView::setDate( const QDate &date ) { mDate = date; emit setDateSignal( date ); } void JournalDateView::clear() { qDeleteAll( mEntries ); mEntries.clear(); } // should only be called by the JournalView now. void JournalDateView::addJournal( const Akonadi::Item &j ) { QMap::Iterator pos = mEntries.find( j.id() ); if ( pos != mEntries.end() ) { return; } QWidget *container = new QWidget( this ); QHBoxLayout *layout = new QHBoxLayout( container ); layout->addStretch( 1 ); JournalFrame *entry = new JournalFrame( j, mCalendar, this ); layout->addWidget( entry, 3/*stretch*/ ); layout->addStretch( 1 ); entry->show(); entry->setDate( mDate ); entry->setIncidenceChanger( mChanger ); mEntries.insert( j.id(), entry ); connect( this, SIGNAL(setIncidenceChangerSignal(Akonadi::IncidenceChanger*)), entry, SLOT(setIncidenceChanger(Akonadi::IncidenceChanger*)) ); connect( this, SIGNAL(setDateSignal(QDate)), entry, SLOT(setDate(QDate)) ); connect( entry, SIGNAL(deleteIncidence(Akonadi::Item)), this, SIGNAL(deleteIncidence(Akonadi::Item)) ); connect( entry, SIGNAL(editIncidence(Akonadi::Item)), this, SIGNAL(editIncidence(Akonadi::Item)) ); connect( entry, SIGNAL(incidenceSelected(Akonadi::Item,QDate)), SIGNAL(incidenceSelected(Akonadi::Item,QDate)) ); connect( entry, SIGNAL(printJournal(KCalCore::Journal::Ptr)), SIGNAL(printJournal(KCalCore::Journal::Ptr)) ); } Akonadi::Item::List JournalDateView::journals() const { Akonadi::Item::List l; Q_FOREACH ( const JournalFrame *const i, mEntries ) { l.push_back( i->journal() ); } return l; } void JournalDateView::setIncidenceChanger( Akonadi::IncidenceChanger *changer ) { mChanger = changer; emit setIncidenceChangerSignal( changer ); } void JournalDateView::emitNewJournal() { emit newJournal( mDate ); } void JournalDateView::journalEdited( const Akonadi::Item &journal ) { QMap::Iterator pos = mEntries.find( journal.id() ); if ( pos == mEntries.end() ) { return; } pos.value()->setJournal( journal ); } void JournalDateView::journalDeleted( const Akonadi::Item &journal ) { QMap::Iterator pos = mEntries.find( journal.id() ); if ( pos == mEntries.end() ) { return; } delete pos.value(); mEntries.remove( journal.id() ); } JournalFrame::JournalFrame( const Akonadi::Item &j, - const Akonadi::ETMCalendar::Ptr &calendar, + const MultiViewCalendar::Ptr &calendar, QWidget *parent ) : QFrame( parent ), mJournal( j ), mCalendar( calendar ) { mDirty = false; mWriteInProgress = false; mChanger = 0; QVBoxLayout *verticalLayout = new QVBoxLayout( this ); verticalLayout->setSpacing( KDialog::spacingHint() ); verticalLayout->setMargin( KDialog::marginHint() ); mBrowser = new KTextBrowser( this ); mBrowser->viewport()->installEventFilter( this ); mBrowser->setFrameStyle( QFrame::NoFrame ); verticalLayout->addWidget( mBrowser ); QHBoxLayout *buttonsLayout = new QHBoxLayout(); verticalLayout->addLayout( buttonsLayout ); buttonsLayout->addStretch(); mEditButton = new QPushButton( this ); mEditButton->setObjectName( QLatin1String("editButton") ); mEditButton->setText( i18n( "&Edit" ) ); mEditButton->setIcon( SmallIcon( QLatin1String("document-properties") ) ); mEditButton->setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed ); mEditButton->setToolTip( i18n( "Edit this journal entry" ) ); mEditButton->setWhatsThis( i18n( "Opens an editor dialog for this journal entry" ) ); buttonsLayout->addWidget( mEditButton ); connect( mEditButton, SIGNAL(clicked()), this, SLOT(editItem()) ); mDeleteButton = new QPushButton( this ); mDeleteButton->setObjectName( QLatin1String("deleteButton") ); mDeleteButton->setText( i18n( "&Delete" ) ); QPixmap pix = SmallIcon( QLatin1String("edit-delete") ); mDeleteButton->setIcon( pix ); mDeleteButton->setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed ); mDeleteButton->setToolTip( i18n( "Delete this journal entry" ) ); mDeleteButton->setWhatsThis( i18n( "Delete this journal entry" ) ); buttonsLayout->addWidget( mDeleteButton ); connect( mDeleteButton, SIGNAL(pressed()), this, SLOT(deleteItem()) ); mPrintButton = new QPushButton( this ); mPrintButton->setText( i18n( "&Print" ) ); mPrintButton->setObjectName( QLatin1String("printButton") ); mPrintButton->setIcon( SmallIcon( QLatin1String("document-print") ) ); mPrintButton->setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed ); mPrintButton->setToolTip( i18n( "Print this journal entry" ) ); mPrintButton->setWhatsThis( i18n( "Opens a print dialog for this journal entry" ) ); buttonsLayout->addWidget( mPrintButton ); connect( mPrintButton, SIGNAL(clicked()), this, SLOT(printJournal()) ); readJournal( mJournal ); mDirty = false; setFrameStyle( QFrame::Box ); // These probably shouldn't be hardcoded setStyleSheet( QLatin1String("QFrame { border: 1px solid; border-radius: 7px; } ") ); mBrowser->setStyleSheet( QLatin1String("QFrame { border: 0px solid white } ") ); } JournalFrame::~JournalFrame() { } bool JournalFrame::eventFilter ( QObject *object, QEvent *event ) { Q_UNUSED( object ); // object is our KTextBrowser if ( !mJournal.isValid() ) { return false; } switch( event->type() ) { case QEvent::MouseButtonPress: emit incidenceSelected( mJournal, mDate ); break; case QEvent::MouseButtonDblClick: emit editIncidence( mJournal ); break; default: break; } return false; } void JournalFrame::deleteItem() { if ( CalendarSupport::hasJournal( mJournal ) ) { emit deleteIncidence( mJournal ); } } void JournalFrame::editItem() { if ( CalendarSupport::hasJournal( mJournal ) ) { emit editIncidence( mJournal ); } } -void JournalFrame::setCalendar( const Akonadi::ETMCalendar::Ptr &calendar ) +void JournalFrame::setCalendar( const MultiViewCalendar::Ptr &calendar ) { mCalendar = calendar; } void JournalFrame::setDate( const QDate &date ) { mDate = date; } void JournalFrame::setJournal( const Akonadi::Item &journal ) { if ( !CalendarSupport::hasJournal( journal ) ) { return; } mJournal = journal; readJournal( journal ); mDirty = false; } void JournalFrame::setDirty() { mDirty = true; kDebug(); } void JournalFrame::printJournal() { emit printJournal( CalendarSupport::journal( mJournal ) ); } void JournalFrame::readJournal( const Akonadi::Item &j ) { int baseFontSize = KGlobalSettings::generalFont().pointSize(); mJournal = j; const KCalCore::Journal::Ptr journal = CalendarSupport::journal( j ); mBrowser->clear(); QTextCursor cursor = QTextCursor( mBrowser->textCursor() ); cursor.movePosition( QTextCursor::Start ); QTextBlockFormat bodyBlock = QTextBlockFormat( cursor.blockFormat() ); //FIXME: Do padding bodyBlock.setTextIndent( 2 ); QTextCharFormat bodyFormat = QTextCharFormat( cursor.charFormat() ); if ( !journal->summary().isEmpty() ) { QTextCharFormat titleFormat = bodyFormat; titleFormat.setFontWeight( QFont::Bold ); titleFormat.setFontPointSize( baseFontSize + 4 ); cursor.insertText( journal->summary(), titleFormat ); cursor.insertBlock(); } QTextCharFormat dateFormat = bodyFormat; dateFormat.setFontWeight( QFont::Bold ); dateFormat.setFontPointSize( baseFontSize + 1 ); cursor.insertText( KCalUtils::IncidenceFormatter::dateTimeToString( journal->dtStart(), journal->allDay() ), dateFormat ); cursor.insertBlock(); cursor.insertBlock(); cursor.setBlockCharFormat( bodyFormat ); const QString description = journal->description(); if ( journal->descriptionIsRich() ) { mBrowser->insertHtml( description ); } else { mBrowser->insertPlainText( description ); } - if ( mCalendar ) { - mEditButton->setEnabled( mCalendar->hasRight( j, Akonadi::Collection::CanChangeItem ) ); - mDeleteButton->setEnabled( mCalendar->hasRight( j, Akonadi::Collection::CanDeleteItem ) ); + if ( mCalendar->etmCalendar() ) { + mEditButton->setEnabled( mCalendar->etmCalendar()->hasRight( j, Akonadi::Collection::CanChangeItem ) ); + mDeleteButton->setEnabled( mCalendar->etmCalendar()->hasRight( j, Akonadi::Collection::CanDeleteItem ) ); } } diff --git a/calendarviews/journal/journalframe.h b/calendarviews/journal/journalframe.h index 942753217a..d4360ebfd9 100644 --- a/calendarviews/journal/journalframe.h +++ b/calendarviews/journal/journalframe.h @@ -1,143 +1,144 @@ /* This file is part of KOrganizer. Copyright (c) 2001 Cornelius Schumacher Copyright (C) 2003-2004 Reinhold Kainhofer Copyright (c) 2007 Mike McQuaid This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #ifndef CALENDARVIEWS_JOURNALFRAME_H #define CALENDARVIEWS_JOURNALFRAME_H #include -#include #include #include +#include "viewcalendar.h" + #include #include class KTextBrowser; class QPushButton; namespace EventViews { class JournalFrame : public QFrame { Q_OBJECT public: typedef QList List; JournalFrame( const Akonadi::Item &journal, - const Akonadi::ETMCalendar::Ptr &calendar, + const MultiViewCalendar::Ptr &calendar, QWidget *parent ); ~JournalFrame(); bool eventFilter ( QObject *, QEvent * ); void setJournal( const Akonadi::Item &journal ); Akonadi::Item journal() const { return mJournal; } - void setCalendar( const Akonadi::ETMCalendar::Ptr & ); + void setCalendar( const MultiViewCalendar::Ptr & ); QDate date() const { return mDate; } void clear(); void readJournal( const Akonadi::Item &journal ); protected Q_SLOTS: void setDirty(); void deleteItem(); void editItem(); void printJournal(); public Q_SLOTS: void setIncidenceChanger( Akonadi::IncidenceChanger *changer ) { mChanger = changer; } void setDate( const QDate &date ); Q_SIGNALS: void printJournal( const KCalCore::Journal::Ptr &); void deleteIncidence( const Akonadi::Item & ); void editIncidence( const Akonadi::Item & ); void incidenceSelected( const Akonadi::Item &, const QDate & ); protected: void clearFields(); private: Akonadi::Item mJournal; - Akonadi::ETMCalendar::Ptr mCalendar; + MultiViewCalendar::Ptr mCalendar; QDate mDate; bool mReadOnly; KTextBrowser *mBrowser; QPushButton *mEditButton; QPushButton *mDeleteButton; QPushButton *mPrintButton; bool mDirty; bool mWriteInProgress; Akonadi::IncidenceChanger *mChanger; }; class JournalDateView : public KVBox { Q_OBJECT public: typedef QList List; - JournalDateView( const Akonadi::ETMCalendar::Ptr & , QWidget *parent ); + JournalDateView( const MultiViewCalendar::Ptr & , QWidget *parent ); ~JournalDateView(); void addJournal( const Akonadi::Item &journal ); Akonadi::Item::List journals() const; void setDate( const QDate &date ); QDate date() const { return mDate; } void clear(); Q_SIGNALS: void setIncidenceChangerSignal( Akonadi::IncidenceChanger *changer ); void setDateSignal( const QDate & ); void flushEntries(); void editIncidence( const Akonadi::Item &journal ); void deleteIncidence( const Akonadi::Item &journal ); void newJournal( const QDate & ); void incidenceSelected( const Akonadi::Item &, const QDate & ); void printJournal( const KCalCore::Journal::Ptr &); public Q_SLOTS: void emitNewJournal(); void setIncidenceChanger( Akonadi::IncidenceChanger *changer ); void journalEdited( const Akonadi::Item & ); void journalDeleted( const Akonadi::Item & ); private: - Akonadi::ETMCalendar::Ptr mCalendar; + MultiViewCalendar::Ptr mCalendar; QDate mDate; QMap mEntries; Akonadi::IncidenceChanger *mChanger; }; } #endif diff --git a/calendarviews/journal/journalview.cpp b/calendarviews/journal/journalview.cpp index 963bfb92a7..2a589004a7 100644 --- a/calendarviews/journal/journalview.cpp +++ b/calendarviews/journal/journalview.cpp @@ -1,216 +1,218 @@ /* This file is part of KOrganizer. Copyright (c) 2001 Cornelius Schumacher Copyright (C) 2003-2004 Reinhold Kainhofer This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ // View of Journal entries #include "journalview.h" #include "journalframe.h" #include #include #include #include #include using namespace EventViews; JournalView::JournalView( QWidget *parent ) : EventView( parent ), mChanger( 0 ) { QVBoxLayout *topLayout = new QVBoxLayout( this ); topLayout->setMargin( 0 ); mSA = new QScrollArea( this ); mVBox = new KVBox( mSA->viewport() ); mSA->setHorizontalScrollBarPolicy( Qt::ScrollBarAlwaysOff ); mSA->setWidgetResizable ( true ); mSA->setWidget( mVBox ); topLayout->addWidget( mSA ); installEventFilter( this ); } JournalView::~JournalView() { } void JournalView::appendJournal( const Akonadi::Item &journal, const QDate &dt ) { JournalDateView *entry = 0; if ( mEntries.contains( dt ) ) { entry = mEntries[dt]; } else { - entry = new JournalDateView( calendar(), mVBox ); + entry = new JournalDateView( viewCalendar(), mVBox ); entry->setDate( dt ); entry->setIncidenceChanger( mChanger ); entry->show(); connect( this, SIGNAL(flushEntries()), entry, SIGNAL(flushEntries()) ); connect( this, SIGNAL(setIncidenceChangerSignal(Akonadi::IncidenceChanger*)), entry, SLOT(setIncidenceChanger(Akonadi::IncidenceChanger*)) ); connect( this, SIGNAL(journalEdited(Akonadi::Item)), entry, SLOT(journalEdited(Akonadi::Item)) ); connect( this, SIGNAL(journalDeleted(Akonadi::Item)), entry, SLOT(journalDeleted(Akonadi::Item)) ); connect( entry, SIGNAL(editIncidence(Akonadi::Item, KDateTime)), this, SIGNAL(editIncidenceSignal(Akonadi::Item, KDateTime)) ); connect( entry, SIGNAL(deleteIncidence(Akonadi::Item)), this, SIGNAL(deleteIncidenceSignal(Akonadi::Item)) ); connect( entry, SIGNAL(newJournal(QDate)), this, SIGNAL(newJournalSignal(QDate)) ); connect( entry, SIGNAL(incidenceSelected(Akonadi::Item,QDate)), SIGNAL(incidenceSelected(Akonadi::Item,QDate)) ); connect( entry, SIGNAL(printJournal(KCalCore::Journal::Ptr)), SIGNAL(printJournal(KCalCore::Journal::Ptr)) ); mEntries.insert( dt, entry ); } if ( entry && CalendarSupport::hasJournal( journal ) ) { entry->addJournal( journal ); } } int JournalView::currentDateCount() const { return mEntries.size(); } Akonadi::Item::List JournalView::selectedIncidences() const { // We don't have a selection in the journal view. // FIXME: The currently edited journal is the selected incidence... Akonadi::Item::List eventList; return eventList; } void JournalView::clearEntries() { //kDebug(5850) << "JournalView::clearEntries()"; QMap::Iterator it; for ( it = mEntries.begin(); it != mEntries.end(); ++it ) { delete it.value(); } mEntries.clear(); } void JournalView::updateView() { QMap::Iterator it = mEntries.end(); while ( it != mEntries.begin() ) { --it; it.value()->clear(); - const KCalCore::Journal::List journals = calendar()->journals( it.key() ); + //TODO: do not use akonadi items + const KCalCore::Journal::List journals = etmCalendar()->journals( it.key() ); kDebug() << "updateview found" << journals.count(); Q_FOREACH ( const KCalCore::Journal::Ptr &journal, journals ) { - Akonadi::Item item = calendar()->item( journal ); + Akonadi::Item item = etmCalendar()->item( journal ); it.value()->addJournal( item ); } } } void JournalView::flushView() { emit flushEntries(); } void JournalView::showDates( const QDate &start, const QDate &end, const QDate & ) { clearEntries(); if ( enditem( journal ); + Akonadi::Item item = etmCalendar()->item( journal ); appendJournal( item, d ); } if ( jnls.isEmpty() ) { // create an empty dateentry widget //updateView(); //kDebug() << "Appended null journal"; appendJournal( Akonadi::Item(), d ); } } } void JournalView::showIncidences( const Akonadi::Item::List &incidences, const QDate &date ) { Q_UNUSED( date ); clearEntries(); Q_FOREACH ( const Akonadi::Item &i, incidences ) { if ( const KCalCore::Journal::Ptr j = CalendarSupport::journal( i ) ) { appendJournal( i, j->dtStart().date() ); } } } void JournalView::changeIncidenceDisplay( const Akonadi::Item &incidence, Akonadi::IncidenceChanger::ChangeType changeType ) { if ( KCalCore::Journal::Ptr journal = CalendarSupport::journal( incidence ) ) { switch ( changeType ) { case Akonadi::IncidenceChanger::ChangeTypeCreate: appendJournal( incidence, journal->dtStart().date() ); break; case Akonadi::IncidenceChanger::ChangeTypeModify: emit journalEdited( incidence ); break; case Akonadi::IncidenceChanger::ChangeTypeDelete: emit journalDeleted( incidence ); break; default: kWarning() << "Illegal change type" << changeType; } } } void JournalView::setIncidenceChanger( Akonadi::IncidenceChanger *changer ) { mChanger = changer; emit setIncidenceChangerSignal( changer ); } void JournalView::newJournal() { emit newJournalSignal( QDate::currentDate() ); } bool JournalView::eventFilter( QObject *object, QEvent *event ) { Q_UNUSED( object ); switch ( event->type() ) { case QEvent::MouseButtonDblClick: emit newJournalSignal( QDate() ); return true; default: return false; } } diff --git a/calendarviews/list/listview.cpp b/calendarviews/list/listview.cpp index 39cd794770..58e33b6f27 100644 --- a/calendarviews/list/listview.cpp +++ b/calendarviews/list/listview.cpp @@ -1,608 +1,608 @@ /* Copyright (c) 1999 Preston Brown Copyright (c) 2000,2001 Cornelius Schumacher Copyright (c) 2003-2004 Reinhold Kainhofer Copyright (c) 2010 Sérgio Martins Copyright (c) 2012-2013 Allen Winter This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ //TODO: put a reminder and/or recurs icon on the item? #include "listview.h" #include "helper.h" #include #include #include #include #include #include #include #include #include #include using namespace EventViews; using namespace KCalCore; using namespace KCalUtils; enum { Summary_Column = 0, StartDateTime_Column, EndDateTime_Column, Categories_Column, Dummy_EOF_Column // Dummy enum value for iteration purposes only. Always keep at the end. }; static QString cleanSummary( const QString &summary, const KDateTime &next ) { QString retStr = summary; retStr.replace( QLatin1Char( '\n' ), QLatin1Char( ' ' ) ); if ( next.isValid() ) { const QString dateStr = KGlobal::locale()->formatDate( next.toTimeSpec( CalendarSupport::KCalPrefs::instance()->timeSpec() ).date(), KLocale::ShortDate ); retStr = i18nc( "%1 is an item summary. %2 is the date when this item reoccurs", "%1 (next: %2)", retStr, dateStr ); } return retStr; } class ListViewItem : public QTreeWidgetItem { public: ListViewItem( const Akonadi::Item &incidence, QTreeWidget *parent ) : QTreeWidgetItem( parent ), mTreeWidget( parent ), mIncidence( incidence ) { } bool operator<( const QTreeWidgetItem & other ) const; const QTreeWidget *mTreeWidget; const Akonadi::Item mIncidence; KDateTime start; KDateTime end; }; bool ListViewItem::operator<( const QTreeWidgetItem &other ) const { const ListViewItem *otheritem = static_cast( &other ); switch( treeWidget()->sortColumn() ) { case StartDateTime_Column: { return otheritem->start < start; } case EndDateTime_Column: { KDateTime thisEnd; Incidence::Ptr thisInc = CalendarSupport::incidence( mIncidence ); thisEnd = thisInc->dateTime( Incidence::RoleEnd ); KDateTime otherEnd; Incidence::Ptr otherInc = CalendarSupport::incidence( otheritem->mIncidence ); otherEnd = otherInc->dateTime( Incidence::RoleEnd ); return otherEnd < thisEnd; } default: return QTreeWidgetItem::operator < ( other ); } } class ListView::Private { public: Private() { } ~Private() { } void addIncidences( const Akonadi::ETMCalendar::Ptr &calendar, const KCalCore::Incidence::List &incidenceList, const QDate &date ); void addIncidence( const Akonadi::ETMCalendar::Ptr &calendar, const KCalCore::Incidence::Ptr &, const QDate &date ); void addIncidence( const Akonadi::ETMCalendar::Ptr &calendar, const Akonadi::Item &, const QDate &date ); ListViewItem *getItemForIncidence( const Akonadi::Item & ); QTreeWidget *mTreeWidget; ListViewItem *mActiveItem; QHash mItems; QHash mDateList; QDate mStartDate; QDate mEndDate; DateList mSelectedDates; // if it's non interactive we disable context menu, and incidence editing bool mIsNonInteractive; class ListItemVisitor; }; /** This class provides the initialization of a ListViewItem for calendar components using the Incidence::Visitor. */ class ListView::Private::ListItemVisitor : public KCalCore::Visitor { public: ListItemVisitor( ListViewItem *item, QDate dt ) : mItem( item ), mStartDate( dt ) { } ~ListItemVisitor() { } bool visit( Event::Ptr ); bool visit( Todo::Ptr ); bool visit( Journal::Ptr ); bool visit( FreeBusy::Ptr ) { // to inhibit hidden virtual compile warning return true; }; private: ListViewItem *mItem; QDate mStartDate; }; bool ListView::Private::ListItemVisitor::visit( Event::Ptr e ) { QPixmap eventPxmp; if ( e->customProperty( "KABC", "ANNIVERSARY" ) == QLatin1String( "YES" ) ) { eventPxmp = cachedSmallIcon( QLatin1String( "view-calendar-wedding-anniversary" ) ); } else if ( e->customProperty( "KABC", "BIRTHDAY" ) == QLatin1String( "YES" ) ) { eventPxmp = cachedSmallIcon( QLatin1String( "view-calendar-birthday" ) ); } else { eventPxmp = cachedSmallIcon( e->iconName() ); } mItem->setIcon( Summary_Column, eventPxmp ); KDateTime next; mItem->start = e->dtStart(); mItem->end = e->dtEnd(); if ( e->recurs() ) { const int duration = e->dtStart().secsTo( e->dtEnd() ); KDateTime kdt = KDateTime::currentDateTime( CalendarSupport::KCalPrefs::instance()->timeSpec() ); kdt = kdt.addSecs( -1 ); mItem->start.setDate( e->recurrence()->getNextDateTime( kdt ).date() ); mItem->end = mItem->start.addSecs( duration ); next = e->recurrence()->getNextDateTime( mItem->start ); } mItem->setText( Summary_Column, cleanSummary( e->summary(), next ) ); mItem->setText( StartDateTime_Column, IncidenceFormatter::dateTimeToString( mItem->start, e->allDay(), true, CalendarSupport::KCalPrefs::instance()->timeSpec() ) ); mItem->setText( EndDateTime_Column, IncidenceFormatter::dateTimeToString( mItem->end, e->allDay(), true, CalendarSupport::KCalPrefs::instance()->timeSpec() ) ); mItem->setText( Categories_Column, e->categoriesStr() ); return true; } bool ListView::Private::ListItemVisitor::visit( Todo::Ptr t ) { mItem->setIcon( Summary_Column, cachedSmallIcon( t->iconName() ) ); mItem->setText( Summary_Column, cleanSummary( t->summary(), KDateTime() ) ); if ( t->hasStartDate() ) { mItem->setText( StartDateTime_Column, IncidenceFormatter::dateTimeToString( t->dtStart(), t->allDay(), true, CalendarSupport::KCalPrefs::instance()->timeSpec() ) ); } else { mItem->setText( StartDateTime_Column, QLatin1String( "---" ) ); } if ( t->hasDueDate() ) { mItem->setText( EndDateTime_Column, IncidenceFormatter::dateTimeToString( t->dtDue(), t->allDay(), true, CalendarSupport::KCalPrefs::instance()->timeSpec() ) ); } else { mItem->setText( EndDateTime_Column, QLatin1String( "---" ) ); } mItem->setText( Categories_Column, t->categoriesStr() ); return true; } bool ListView::Private::ListItemVisitor::visit( Journal::Ptr j ) { static const QPixmap jrnalPxmp = SmallIcon( j->iconName() ); mItem->setIcon( Summary_Column, jrnalPxmp ); if ( j->summary().isEmpty() ) { mItem->setText( Summary_Column, cleanSummary( j->description().section( QLatin1Char( '\n' ), 0, 0 ), KDateTime() ) ); } else { mItem->setText( Summary_Column, cleanSummary( j->summary(), KDateTime() ) ); } mItem->setText( StartDateTime_Column, IncidenceFormatter::dateTimeToString( j->dtStart(), j->allDay(), true, CalendarSupport::KCalPrefs::instance()->timeSpec() ) ); return true; } ListView::ListView( const Akonadi::ETMCalendar::Ptr &calendar, QWidget *parent, bool nonInteractive ) : EventView( parent ), d( new Private() ) { setCalendar( calendar ); d->mActiveItem = 0; d->mIsNonInteractive = nonInteractive; d->mTreeWidget = new QTreeWidget( this ); d->mTreeWidget->setColumnCount( 4 ); d->mTreeWidget->setSortingEnabled( true ); d->mTreeWidget->headerItem()->setText( Summary_Column, i18n( "Summary" ) ); d->mTreeWidget->headerItem()->setText( StartDateTime_Column, i18n( "Start Date/Time" ) ); d->mTreeWidget->headerItem()->setText( EndDateTime_Column, i18n( "End Date/Time" ) ); d->mTreeWidget->headerItem()->setText( Categories_Column, i18n( "Categories" ) ); d->mTreeWidget->setWordWrap( true ); d->mTreeWidget->setAllColumnsShowFocus( true ); d->mTreeWidget->setContextMenuPolicy( Qt::CustomContextMenu ); d->mTreeWidget->setRootIsDecorated( false ); QBoxLayout *layoutTop = new QVBoxLayout( this ); layoutTop->setMargin( 0 ); layoutTop->addWidget( d->mTreeWidget ); QObject::connect( d->mTreeWidget, SIGNAL(doubleClicked(QModelIndex)), SLOT(defaultItemAction(QModelIndex)) ); QObject::connect( d->mTreeWidget, SIGNAL(customContextMenuRequested(QPoint)), SLOT(popupMenu(QPoint)) ); QObject::connect( d->mTreeWidget, SIGNAL(itemSelectionChanged()), SLOT(processSelectionChange()) ); // TODO //d->mTreeWidget->restoreLayout( KOGlobals::self()->config(), "ListView Layout" ); d->mSelectedDates.append( QDate::currentDate() ); updateView(); } ListView::~ListView() { delete d; } int ListView::currentDateCount() const { return d->mSelectedDates.count(); } Akonadi::Item::List ListView::selectedIncidences() const { Akonadi::Item::List eventList; QTreeWidgetItem *item = d->mTreeWidget->selectedItems().isEmpty() ? 0 : d->mTreeWidget->selectedItems().first() ; if ( item ) { ListViewItem *i = static_cast( item ); eventList.append( i->mIncidence ); } return eventList; } DateList ListView::selectedIncidenceDates() const { return d->mSelectedDates; } void ListView::updateView() { static int maxLen = 38; /* Set the width of the summary column to show 'maxlen' chars, at most */ int width = qMin( maxLen * fontMetrics().averageCharWidth(), maxLen * 12 ); width += 24; //for the icon d->mTreeWidget->setColumnWidth( Summary_Column, width ); for ( int col = StartDateTime_Column; col < Dummy_EOF_Column; ++col ) { d->mTreeWidget->resizeColumnToContents( col ); } d->mTreeWidget->sortItems( StartDateTime_Column, Qt::DescendingOrder ); } void ListView::showDates( const QDate &start, const QDate &end, const QDate &preferredMonth ) { Q_UNUSED( preferredMonth ); clear(); d->mStartDate = start; d->mEndDate = end; KDateTime kStart( start ); const QString startStr = KGlobal::locale()->formatDate( kStart.toTimeSpec( CalendarSupport::KCalPrefs::instance()->timeSpec() ).date(), KLocale::ShortDate ); KDateTime kEnd( end ); const QString endStr = KGlobal::locale()->formatDate( kEnd.toTimeSpec( CalendarSupport::KCalPrefs::instance()->timeSpec() ).date(), KLocale::ShortDate ); d->mTreeWidget->headerItem()->setText( Summary_Column, i18n( "Summary [%1 - %2]", startStr, endStr ) ); QDate date = start; while ( date <= end ) { - d->addIncidences( calendar(), calendar()->incidences( date ), date ); + d->addIncidences( etmCalendar(), etmCalendar()->incidences( date ), date ); d->mSelectedDates.append( date ); date = date.addDays( 1 ); } updateView(); emit incidenceSelected( Akonadi::Item(), QDate() ); } void ListView::showAll() { - d->addIncidences( calendar(), calendar()->incidences(), QDate() ); + d->addIncidences( etmCalendar(), etmCalendar()->incidences(), QDate() ); } void ListView::Private::addIncidences( const Akonadi::ETMCalendar::Ptr &calendar, const KCalCore::Incidence::List &incidences, const QDate &date ) { Q_FOREACH ( const KCalCore::Incidence::Ptr &incidence, incidences ) { addIncidence( calendar, incidence, date ); } } void ListView::Private::addIncidence( const Akonadi::ETMCalendar::Ptr &calendar, const Akonadi::Item &item, const QDate &date ) { Q_ASSERT( calendar ); if ( item.isValid() && item.hasPayload() ) { addIncidence( calendar, item.payload(), date ); } } void ListView::Private::addIncidence( const Akonadi::ETMCalendar::Ptr &calendar, const KCalCore::Incidence::Ptr &incidence, const QDate &date ) { if ( !incidence ) { return; } Akonadi::Item aitem = calendar->item( incidence ); if ( !aitem.isValid() || mItems.contains( aitem.id() ) ) { return; } mDateList.insert( aitem.id(), date ); mItems.insert( aitem.id(), aitem ); Incidence::Ptr tinc = incidence; if ( tinc->customProperty( "KABC", "BIRTHDAY" ) == QLatin1String( "YES" ) || tinc->customProperty( "KABC", "ANNIVERSARY" ) == QLatin1String( "YES" ) ) { const int years = EventViews::yearDiff( tinc->dtStart().date(), mEndDate ); if ( years > 0 ) { tinc = Incidence::Ptr( incidence->clone() ); tinc->setReadOnly( false ); tinc->setSummary( i18np( "%2 (1 year)", "%2 (%1 years)", years, cleanSummary( incidence->summary(), KDateTime() ) ) ); tinc->setReadOnly( true ); } } ListViewItem *item = new ListViewItem( aitem, mTreeWidget ); // set tooltips for ( int col = 0; col < Dummy_EOF_Column; ++col ) { item->setToolTip( col, IncidenceFormatter::toolTipStr( CalendarSupport::displayName( calendar.data(), aitem.parentCollection() ), incidence ) ); } ListItemVisitor v( item, mStartDate ); if ( !tinc->accept( v, tinc ) ) { delete item; return; } item->setData( 0, Qt::UserRole, QVariant( aitem.id() ) ); } void ListView::showIncidences( const Akonadi::Item::List &itemList, const QDate &date ) { clear(); - d->addIncidences( calendar(), CalendarSupport::incidencesFromItems( itemList ), date ); + d->addIncidences( etmCalendar(), CalendarSupport::incidencesFromItems( itemList ), date ); updateView(); // After new creation of list view no events are selected. emit incidenceSelected( Akonadi::Item(), date ); } void ListView::changeIncidenceDisplay( const Akonadi::Item &aitem, int action ) { const Incidence::Ptr incidence = CalendarSupport::incidence( aitem ); ListViewItem *item; QDate f = d->mSelectedDates.first(); QDate l = d->mSelectedDates.last(); QDate date; if ( CalendarSupport::hasTodo( aitem ) ) { date = CalendarSupport::todo( aitem )->dtDue(). toTimeSpec( CalendarSupport::KCalPrefs::instance()->timeSpec() ).date(); } else { date = incidence->dtStart(). toTimeSpec( CalendarSupport::KCalPrefs::instance()->timeSpec() ).date(); } switch( action ) { case Akonadi::IncidenceChanger::ChangeTypeCreate: { if ( date >= f && date <= l ) { - d->addIncidence( calendar(), aitem, date ); + d->addIncidence( etmCalendar(), aitem, date ); } break; } case Akonadi::IncidenceChanger::ChangeTypeModify: { item = d->getItemForIncidence( aitem ); if ( item ) { delete item; d->mItems.remove( aitem.id() ); d->mDateList.remove( aitem.id() ); } if ( date >= f && date <= l ) { - d->addIncidence( calendar(), aitem, date ); + d->addIncidence( etmCalendar(), aitem, date ); } break; } case Akonadi::IncidenceChanger::ChangeTypeDelete: { item = d->getItemForIncidence( aitem ); delete item; break; } default: kDebug() << "Illegal action" << action; } } ListViewItem *ListView::Private::getItemForIncidence( const Akonadi::Item &aitem ) { int index = 0; while ( QTreeWidgetItem *it = mTreeWidget->topLevelItem( index ) ) { ListViewItem *item = static_cast( it ); if ( item->mIncidence.id() == aitem.id() ) { return item; } ++index; } return 0; } void ListView::defaultItemAction( const QModelIndex &index ) { if ( !d->mIsNonInteractive ) { // Get the first column, it has our Akonadi::Id const QModelIndex col0Idx = d->mTreeWidget->model()->index( index.row(), 0 ); Akonadi::Item::Id id = d->mTreeWidget->model()->data( col0Idx, Qt::UserRole ).toLongLong(); defaultAction( d->mItems.value( id ) ); } } void ListView::defaultItemAction( const Akonadi::Item::Id id ) { if ( !d->mIsNonInteractive ) { defaultAction( d->mItems.value( id ) ); } } void ListView::popupMenu( const QPoint &point ) { d->mActiveItem = static_cast( d->mTreeWidget->itemAt( point ) ); if ( d->mActiveItem && !d->mIsNonInteractive ) { const Akonadi::Item aitem = d->mActiveItem->mIncidence; // FIXME: For recurring incidences we don't know the date of this // occurrence, there's no reference to it at all! emit showIncidencePopupSignal( aitem, CalendarSupport::incidence( aitem )->dtStart().date() ); } else { emit showNewEventPopupSignal(); } } void ListView::readSettings( KConfig *config ) { KConfigGroup cfgGroup = config->group( "ListView Layout" ); const QByteArray state = cfgGroup.readEntry( "ViewState", QByteArray() ); d->mTreeWidget->header()->restoreState( state ); } void ListView::writeSettings( KConfig *config ) { const QByteArray state = d->mTreeWidget->header()->saveState(); KConfigGroup cfgGroup = config->group( "ListView Layout" ); cfgGroup.writeEntry( "ViewState", state ); } void ListView::processSelectionChange() { if ( !d->mIsNonInteractive ) { ListViewItem *item; if ( d->mTreeWidget->selectedItems().isEmpty() ) { item = 0; } else { item = static_cast( d->mTreeWidget->selectedItems().first() ); } if ( !item ) { emit incidenceSelected( Akonadi::Item(), QDate() ); } else { emit incidenceSelected( item->mIncidence, d->mDateList.value( item->mIncidence.id() ) ); } } } void ListView::clearSelection() { d->mTreeWidget->clearSelection(); } void ListView::clear() { d->mSelectedDates.clear(); d->mTreeWidget->clear(); d->mDateList.clear(); d->mItems.clear(); } QSize ListView::sizeHint() const { const QSize s = EventView::sizeHint(); return QSize( s.width() + style()->pixelMetric( QStyle::PM_ScrollBarExtent ) + 1, s.height() ); } diff --git a/calendarviews/month/monthitem.cpp b/calendarviews/month/monthitem.cpp index 364c1cde65..63cd690766 100644 --- a/calendarviews/month/monthitem.cpp +++ b/calendarviews/month/monthitem.cpp @@ -1,777 +1,773 @@ /* Copyright (c) 2008 Bruno Virlet This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #include "monthitem.h" #include "helper.h" #include "monthgraphicsitems.h" #include "monthscene.h" #include "monthview.h" #include "prefs.h" #include "prefs_base.h" // Ugly, but needed for the Enums #include #include #include #include #include #include using namespace EventViews; using namespace KCalCore; MonthItem::MonthItem( MonthScene *monthScene ) : mMonthScene( monthScene ), mSelected( false ), mMoving( false ), mResizing( false ) { } MonthItem::~MonthItem() { deleteAll(); } void MonthItem::deleteAll() { qDeleteAll( mMonthGraphicsItemList ); mMonthGraphicsItemList.clear(); } QWidget *MonthItem::parentWidget() const { return mMonthScene ? mMonthScene->monthView() : 0; } void MonthItem::updateMonthGraphicsItems() { // Remove all items qDeleteAll( mMonthGraphicsItemList ); mMonthGraphicsItemList.clear(); const QDate monthStartDate = startDate(); const QDate monthEndDate = endDate(); // For each row of the month view, create an item to build the whole // MonthItem's MonthGraphicsItems. for ( QDate d = mMonthScene->mMonthView->actualStartDateTime().date(); d < mMonthScene->mMonthView->actualEndDateTime().date(); d = d.addDays( 7 ) ) { QDate end = d.addDays( 6 ); int span; QDate start; if ( monthStartDate <= d && monthEndDate >= end ) { // MonthItem takes the whole line span = 6; start = d; } else if ( monthStartDate >= d && monthEndDate <= end ) { // starts and ends on this line start = monthStartDate; span = daySpan(); } else if ( d <= monthEndDate && monthEndDate <= end ) { // MonthItem ends on this line span = mMonthScene->getLeftSpan( monthEndDate ); start = d; } else if ( d <= monthStartDate && monthStartDate <= end ) { // MonthItem begins on this line span = mMonthScene->getRightSpan( monthStartDate ); start = monthStartDate; } else { // MonthItem is not on the line continue; } // A new item needs to be created MonthGraphicsItem *newItem = new MonthGraphicsItem( this ); mMonthGraphicsItemList << newItem; newItem->setStartDate( start ); newItem->setDaySpan( span ); } if ( isMoving() || isResizing() ) { setZValue( 100 ); } else { setZValue( 0 ); } } void MonthItem::beginResize() { mOverrideDaySpan = daySpan(); mOverrideStartDate = startDate(); mResizing = true; setZValue( 100 ); } void MonthItem::endResize() { setZValue( 0 ); mResizing = false; // startDate() and daySpan() return real values again if ( mOverrideStartDate != startDate() || mOverrideDaySpan != daySpan() ) { finalizeResize( mOverrideStartDate, mOverrideStartDate.addDays( mOverrideDaySpan ) ); } } void MonthItem::beginMove() { mOverrideDaySpan = daySpan(); mOverrideStartDate = startDate(); mMoving = true; setZValue( 100 ); } void MonthItem::endMove() { setZValue( 0 ); mMoving = false; // startDate() and daySpan() return real values again if ( mOverrideStartDate != startDate() ) { finalizeMove( mOverrideStartDate ); } } bool MonthItem::resizeBy( int offsetToPreviousDate ) { bool ret = false; if ( mMonthScene->resizeType() == MonthScene::ResizeLeft ) { if ( mOverrideDaySpan - offsetToPreviousDate >= 0 ) { mOverrideStartDate = mOverrideStartDate.addDays( offsetToPreviousDate ); mOverrideDaySpan = mOverrideDaySpan - offsetToPreviousDate; ret = true; } } else if ( mMonthScene->resizeType() == MonthScene::ResizeRight ) { if ( mOverrideDaySpan + offsetToPreviousDate >= 0 ) { mOverrideDaySpan = mOverrideDaySpan + offsetToPreviousDate; ret = true; } } if ( ret ) { updateMonthGraphicsItems(); } return ret; } void MonthItem::moveBy( int offsetToPreviousDate ) { mOverrideStartDate = mOverrideStartDate.addDays( offsetToPreviousDate ); updateMonthGraphicsItems(); } void MonthItem::updateGeometry() { foreach ( MonthGraphicsItem *item, mMonthGraphicsItemList ) { item->updateGeometry(); } } void MonthItem::setZValue( qreal z ) { foreach ( MonthGraphicsItem *item, mMonthGraphicsItemList ) { item->setZValue( z ); } } QDate MonthItem::startDate() const { if ( isMoving() || isResizing() ) { return mOverrideStartDate; } return realStartDate(); } QDate MonthItem::endDate() const { if ( isMoving() || isResizing() ) { return mOverrideStartDate.addDays( mOverrideDaySpan ); } return realEndDate(); } int MonthItem::daySpan() const { if ( isMoving() || isResizing() ) { return mOverrideDaySpan; } QDateTime start( startDate() ); QDateTime end( endDate() ); if ( start.isValid() && end.isValid() ) { return start.daysTo( end ); } return 0; } bool MonthItem::greaterThan( const MonthItem *e1, const MonthItem *e2 ) { const QDate leftStartDate = e1->startDate(); const QDate rightStartDate = e2->startDate(); if ( !leftStartDate.isValid() || !rightStartDate.isValid() ) { return false; } if ( leftStartDate == rightStartDate ) { const int leftDaySpan = e1->daySpan(); const int rightDaySpan = e2->daySpan(); if ( leftDaySpan == rightDaySpan ) { if ( e1->allDay() && !e2->allDay() ) { return true; } if ( !e1->allDay() && e2->allDay() ) { return false; } return e1->greaterThanFallback( e2 ); } else { return leftDaySpan > rightDaySpan; } } return leftStartDate < rightStartDate; } bool MonthItem::greaterThanFallback( const MonthItem *other ) const { const HolidayMonthItem *h = qobject_cast( other ); // If "other" is a holiday, display it first. return !h; } void MonthItem::updatePosition() { if ( !startDate().isValid() || !endDate().isValid() ) { return; } int firstFreeSpace = 0; for ( QDate d = startDate(); d <= endDate(); d = d.addDays( 1 ) ) { MonthCell *cell = mMonthScene->mMonthCellMap.value( d ); if ( !cell ) { continue; // cell can be null if the item begins outside the month } int firstFreeSpaceTmp = cell->firstFreeSpace(); if ( firstFreeSpaceTmp > firstFreeSpace ) { firstFreeSpace = firstFreeSpaceTmp; } } for ( QDate d = startDate(); d <= endDate(); d = d.addDays( 1 ) ) { MonthCell *cell = mMonthScene->mMonthCellMap.value( d ); if ( !cell ) { continue; } cell->addMonthItem( this, firstFreeSpace ); } mPosition = firstFreeSpace; } QList EventViews::MonthItem::monthGraphicsItems() const { return mMonthGraphicsItemList; } //----------------------------------------------------------------- // INCIDENCEMONTHITEM IncidenceMonthItem::IncidenceMonthItem( MonthScene *monthScene, - const Akonadi::ETMCalendar::Ptr &calendar, - const Akonadi::Item &aitem, + const MultiViewCalendar::Ptr &calendar, const KCalCore::Incidence::Ptr &incidence, const QDate &recurStartDate ) : MonthItem( monthScene ), mCalendar( calendar ), - mIncidence( incidence ), - mAkonadiItemId( aitem.id() ) + mIncidence( incidence ) { - mIsEvent = CalendarSupport::hasEvent( aitem ); - mIsJournal = CalendarSupport::hasJournal( aitem ); - mIsTodo = CalendarSupport::hasTodo( aitem ); + mIsEvent = CalendarSupport::hasEvent( incidence ); + mIsJournal = CalendarSupport::hasJournal( incidence ); + mIsTodo = CalendarSupport::hasTodo( incidence ); KCalCore::Incidence::Ptr inc = mIncidence; if ( inc->customProperty( "KABC", "BIRTHDAY" ) == QLatin1String("YES") || inc->customProperty( "KABC", "ANNIVERSARY" ) == QLatin1String("YES") ) { const int years = EventViews::yearDiff( inc->dtStart().date(), recurStartDate ); if ( years > 0 ) { inc = KCalCore::Incidence::Ptr( inc->clone() ); inc->setReadOnly( false ); inc->setDescription( i18np( "%2 1 year", "%2 %1 years", years, i18n( "Age:" ) ) ); inc->setReadOnly( true ); mIncidence = inc; } } connect( monthScene, SIGNAL(incidenceSelected(Akonadi::Item,QDate)), this, SLOT(updateSelection(Akonadi::Item,QDate)) ); // first set to 0, because it's used in startDate() mRecurDayOffset = 0; if ( ( mIncidence->recurs() || mIncidence->recurrenceId().isValid() ) && startDate().isValid() && recurStartDate.isValid() ) { mRecurDayOffset = startDate().daysTo( recurStartDate ); } } IncidenceMonthItem::~IncidenceMonthItem() { } bool IncidenceMonthItem::greaterThanFallback( const MonthItem *other ) const { const IncidenceMonthItem *o = qobject_cast( other ); if ( !o ) { return MonthItem::greaterThanFallback( other ); } if ( allDay() != o->allDay() ) { return allDay(); } const KCalCore::Incidence::Ptr otherIncidence = o->mIncidence; if ( mIncidence->dtStart().time() != otherIncidence->dtStart().time() ) { return mIncidence->dtStart().time() < otherIncidence->dtStart().time(); } // as a last resort, compare uids - return mIncidence->uid() < otherIncidence->uid(); + return mCalendar->uid(mIncidence) < mCalendar->uid(otherIncidence); } QDate IncidenceMonthItem::realStartDate() const { if ( !mIncidence ) { return QDate(); } const KDateTime dt = mIncidence->dateTime( Incidence::RoleDisplayStart ); const QDate start = dt.isDateOnly() ? dt.date() : dt.toTimeSpec( CalendarSupport::KCalPrefs::instance()->timeSpec() ).date(); return start.addDays( mRecurDayOffset ); } QDate IncidenceMonthItem::realEndDate() const { if ( !mIncidence ) { return QDate(); } const KDateTime dt = mIncidence->dateTime( KCalCore::Incidence::RoleDisplayEnd ); const QDate end = dt.isDateOnly() ? dt.date() : dt.toTimeSpec( CalendarSupport::KCalPrefs::instance()->timeSpec() ).date(); return end.addDays( mRecurDayOffset ); } bool IncidenceMonthItem::allDay() const { return mIncidence->allDay(); } bool IncidenceMonthItem::isMoveable() const { - return monthScene()->mMonthView->calendar()->hasRight( akonadiItem(), + return monthScene()->mMonthView->etmCalendar()->hasRight( akonadiItem(), Akonadi::Collection::CanChangeItem ); } bool IncidenceMonthItem::isResizable() const { - return mIsEvent && monthScene()->mMonthView->calendar()->hasRight( akonadiItem(), + return mIsEvent && monthScene()->mMonthView->etmCalendar()->hasRight( akonadiItem(), Akonadi::Collection::CanChangeItem ); } void IncidenceMonthItem::finalizeMove( const QDate &newStartDate ) { Q_ASSERT( isMoveable() ); if ( startDate().isValid() && newStartDate.isValid() ) { updateDates( startDate().daysTo( newStartDate ), startDate().daysTo( newStartDate ) ); } } void IncidenceMonthItem::finalizeResize( const QDate &newStartDate, const QDate &newEndDate ) { Q_ASSERT( isResizable() ); if ( startDate().isValid() && endDate().isValid() && newStartDate.isValid() && newEndDate.isValid() ) { updateDates( startDate().daysTo( newStartDate ), endDate().daysTo( newEndDate ) ); } } void IncidenceMonthItem::updateDates( int startOffset, int endOffset ) { Akonadi::IncidenceChanger *changer = monthScene()->incidenceChanger(); if ( !changer || ( startOffset == 0 && endOffset == 0 ) ) { kDebug() << changer << startOffset << endOffset; return; } Akonadi::Item item = akonadiItem(); item.setPayload( mIncidence ); if ( mIncidence->recurs() ) { const int res = monthScene()->mMonthView->showMoveRecurDialog( mIncidence, startDate() ); switch ( res ) { case KCalUtils::RecurrenceActions::AllOccurrences: {// All occurrences KCalCore::Incidence::Ptr oldIncidence( mIncidence->clone() ); setNewDates( mIncidence, startOffset, endOffset ); changer->modifyIncidence( item, oldIncidence ); break; } case KCalUtils::RecurrenceActions::SelectedOccurrence: // Just this occurrence case KCalUtils::RecurrenceActions::FutureOccurrences: // All future occurrences { const bool thisAndFuture = (res == KCalUtils::RecurrenceActions::FutureOccurrences); KDateTime occurrenceDate( mIncidence->dtStart() ); occurrenceDate.setDate( startDate() ); KCalCore::Incidence::Ptr newIncidence( KCalCore::Calendar::createException( mIncidence, occurrenceDate, thisAndFuture ) ); if ( newIncidence ) { changer->startAtomicOperation( i18n( "Move occurrence(s)" ) ); setNewDates( newIncidence, startOffset, endOffset ); changer->createIncidence( newIncidence, item.parentCollection(), parentWidget() ); changer->endAtomicOperation(); } else { KMessageBox::sorry( parentWidget(), i18n( "Unable to add the exception item to the calendar. " "No change will be done." ), i18n( "Error Occurred" ) ); } break; } } } else { // Doesn't recur KCalCore::Incidence::Ptr oldIncidence( mIncidence->clone() ); setNewDates( mIncidence, startOffset, endOffset ); changer->modifyIncidence( item, oldIncidence ); } } void IncidenceMonthItem::updateSelection( const Akonadi::Item &incidence, const QDate &date ) { Q_UNUSED( date ); setSelected( incidence == akonadiItem() ); } QString IncidenceMonthItem::text( bool end ) const { QString ret = mIncidence->summary(); if ( !allDay() && !mIsJournal && monthScene()->monthView()->preferences()->showTimeInMonthView() ) { // Prepend the time str to the text QString timeStr; if ( mIsTodo ) { KCalCore::Todo::Ptr todo = mIncidence.staticCast(); timeStr = KCalUtils::IncidenceFormatter::timeToString( todo->dtDue(), true, CalendarSupport::KCalPrefs::instance()->timeSpec() ); } else { if ( !end ) { timeStr = KCalUtils::IncidenceFormatter::timeToString( mIncidence->dtStart(), true, CalendarSupport::KCalPrefs::instance()->timeSpec() ); } else { KCalCore::Event::Ptr event = mIncidence.staticCast(); timeStr = KCalUtils::IncidenceFormatter::timeToString( event->dtEnd(), true, CalendarSupport::KCalPrefs::instance()->timeSpec() ); } } if ( !timeStr.isEmpty() ) { if ( !end ) { ret = timeStr + QLatin1Char(' ') + ret; } else { ret = ret + QLatin1Char(' ') + timeStr; } } } return ret; } QString IncidenceMonthItem::toolTipText( const QDate &date ) const { - return KCalUtils::IncidenceFormatter::toolTipStr( - CalendarSupport::displayName( mCalendar.data(), akonadiItem().parentCollection() ), + return KCalUtils::IncidenceFormatter::toolTipStr( mCalendar->displayName(mIncidence), mIncidence, date, true, CalendarSupport::KCalPrefs::instance()->timeSpec() ); } QList IncidenceMonthItem::icons() const { QList ret; if ( !mIncidence ) { return ret; } bool specialEvent = false; - Akonadi::Item item = akonadiItem(); const QSet icons = monthScene()->monthView()->preferences()->monthViewIcons(); QString customIconName; if ( icons.contains( EventViews::EventView::CalendarCustomIcon ) ) { - const QString iconName = monthScene()->monthView()->iconForItem( item ); + const QString iconName = monthScene()->monthView()->viewCalendar()->iconForIncidence(mIncidence); if ( !iconName.isEmpty() && iconName != QLatin1String("view-calendar") && iconName != QLatin1String("office-calendar") ) { customIconName = iconName; ret << QPixmap( cachedSmallIcon( iconName ) ); } } if ( mIsEvent ) { if ( mIncidence->customProperty( "KABC", "ANNIVERSARY" ) == QLatin1String("YES") ) { specialEvent = true; ret << monthScene()->anniversaryPixmap(); } else if ( mIncidence->customProperty( "KABC", "BIRTHDAY" ) == QLatin1String("YES") ) { specialEvent = true; // Disabling birthday icon because it's the birthday agent's icon // and we allow to display the agent's icon now. //ret << monthScene()->birthdayPixmap(); } // smartins: Disabling the event Pixmap because: // 1. Save precious space so we can read the event's title better. // 2. We don't need a pixmap to tell us an item is an event we // only need one to tell us it's not, as month view was designed for events. // 3. If only to-dos and journals have a pixmap they will be distinguished // from event's much easier. // ret << monthScene()->eventPixmap(); } else if ( ( mIsTodo || mIsJournal ) && icons.contains( mIsTodo ? EventView::TaskIcon : EventView::JournalIcon ) ) { KDateTime occurrenceDateTime = mIncidence->dateTime( Incidence::RoleRecurrenceStart ); occurrenceDateTime.setDate( realStartDate() ); const QString incidenceIconName = mIncidence->iconName( occurrenceDateTime ); if ( customIconName != incidenceIconName ) ret << QPixmap( cachedSmallIcon( incidenceIconName ) ); } if ( icons.contains( EventView::ReadOnlyIcon ) && - !monthScene()->mMonthView->calendar()->hasRight( item, Akonadi::Collection::CanChangeItem ) && + !monthScene()->mMonthView->etmCalendar()->hasRight( akonadiItem(), Akonadi::Collection::CanChangeItem ) && !specialEvent ) { ret << monthScene()->readonlyPixmap(); } /* sorry, this looks too cluttered. disable until we can make something prettier; no idea at this time -- allen */ if ( icons.contains( EventView::ReminderIcon ) && mIncidence->hasEnabledAlarms() && !specialEvent ) { ret << monthScene()->alarmPixmap(); } if ( icons.contains( EventView::RecurringIcon ) && mIncidence->recurs() && !specialEvent ) { ret << monthScene()->recurPixmap(); } //TODO: check what to do with Reply return ret; } QColor IncidenceMonthItem::catColor() const { Q_ASSERT( mIncidence ); const QStringList categories = mIncidence->categories(); QString cat; if ( !categories.isEmpty() ) { cat = categories.first(); } return cat.isEmpty() ? CalendarSupport::KCalPrefs::instance()->unsetCategoryColor() : CalendarSupport::KCalPrefs::instance()->categoryColor( cat ); } QColor IncidenceMonthItem::bgColor() const { QColor bgColor = QColor(); // Default invalid color; PrefsPtr prefs = monthScene()->monthView()->preferences(); if ( mIsTodo && !prefs->todosUseCategoryColors() ) { - Todo::Ptr todo = CalendarSupport::todo( akonadiItem() ); + Todo::Ptr todo = CalendarSupport::todo(mIncidence); Q_ASSERT( todo ); if ( todo ) { const QDate dtRecurrence = // this is dtDue if there's no dtRecurrence todo->dtRecurrence().toTimeSpec( CalendarSupport::KCalPrefs::instance()->timeSpec() ).date(); const QDate today = KDateTime::currentDateTime( CalendarSupport::KCalPrefs::instance()->timeSpec() ).date(); if ( todo->isOverdue() && today > startDate() && startDate() >= dtRecurrence ) { bgColor = prefs->todoOverdueColor(); } else if ( today == startDate() && !todo->isCompleted() && startDate() >= dtRecurrence ) { bgColor = prefs->todoDueTodayColor(); } } } if ( !bgColor.isValid() ) { if ( prefs->monthViewColors() == PrefsBase::MonthItemResourceOnly || prefs->monthViewColors() == PrefsBase::MonthItemResourceInsideCategoryOutside ) { - bgColor = EventViews::resourceColor(akonadiItem(), prefs); + bgColor = mCalendar->resourceColor(mIncidence); } else { bgColor = catColor(); } } if ( !bgColor.isValid() ) { bgColor = Qt::white; } return bgColor; } QColor IncidenceMonthItem::frameColor() const { QColor frameColor; PrefsPtr prefs = monthScene()->monthView()->preferences(); if ( prefs->monthViewColors() == PrefsBase::MonthItemResourceOnly || prefs->monthViewColors() == PrefsBase::MonthItemCategoryInsideResourceOutside || ( mIncidence->categories().isEmpty() && prefs->monthViewColors() == PrefsBase::MonthItemResourceInsideCategoryOutside ) ) { Q_ASSERT( mIncidence ); - frameColor = EventViews::resourceColor(akonadiItem(), prefs); + frameColor = mCalendar->resourceColor(mIncidence); } else { frameColor = catColor(); } return EventView::itemFrameColor( frameColor, selected() ); } Akonadi::Item IncidenceMonthItem::akonadiItem() const { if ( mIncidence ) { - return monthScene()->mMonthView->calendar()->item( mIncidence ); + return monthScene()->mMonthView->viewCalendar()->item( mIncidence ); } else { return Akonadi::Item(); } } KCalCore::Incidence::Ptr IncidenceMonthItem::incidence() const { return mIncidence; } Akonadi::Item::Id IncidenceMonthItem::akonadiItemId() const { - return mAkonadiItemId; + return akonadiItem().id(); } void IncidenceMonthItem::setNewDates( const KCalCore::Incidence::Ptr &incidence, int startOffset, int endOffset ) { if ( mIsTodo ) { // For to-dos endOffset is ignored because it will always be == to startOffset because we only // support moving to-dos, not resizing them. There are no multi-day to-dos. // Lets just call it offset to reduce confusion. const int offset = startOffset; KCalCore::Todo::Ptr todo = incidence.staticCast(); KDateTime due = todo->dtDue(); KDateTime start = todo->dtStart(); if ( due.isValid() ) { // Due has priority over start. // We will only move the due date, unlike events where we move both. due = due.addDays( offset ); todo->setDtDue( due ); if ( start.isValid() && start > due ) { // Start can't be bigger than due. todo->setDtStart( due ); } } else if ( start.isValid() ) { // So we're displaying a to-do that doesn't have due date, only start... start = start.addDays( offset ); todo->setDtStart( start ); } else { // This never happens kWarning() << "Move what? uid:" << todo->uid() << "; summary=" << todo->summary(); } } else { incidence->setDtStart( incidence->dtStart().addDays( startOffset ) ); if ( mIsEvent ) { KCalCore::Event::Ptr event = incidence.staticCast(); event->setDtEnd( event->dtEnd().addDays( endOffset ) ); } } } //----------------------------------------------------------------- // HOLIDAYMONTHITEM HolidayMonthItem::HolidayMonthItem( MonthScene *monthScene, const QDate &date, const QString &name ) : MonthItem( monthScene ), mDate( date ), mName( name ) { } HolidayMonthItem::~HolidayMonthItem() { } bool HolidayMonthItem::greaterThanFallback( const MonthItem *other ) const { const HolidayMonthItem *o = qobject_cast( other ); if ( o ) { return MonthItem::greaterThanFallback( other ); } // always put holidays on top return false; } void HolidayMonthItem::finalizeMove( const QDate &newStartDate ) { Q_UNUSED( newStartDate ); Q_ASSERT( false ); } void HolidayMonthItem::finalizeResize( const QDate &newStartDate, const QDate &newEndDate ) { Q_UNUSED( newStartDate ); Q_UNUSED( newEndDate ); Q_ASSERT( false ); } QList HolidayMonthItem::icons() const { QList ret; ret << monthScene()->holidayPixmap(); return ret; } QColor HolidayMonthItem::bgColor() const { // FIXME: Currently, only this value is settable in the options. // There is a monthHolidaysBackgroundColor() option too. Maybe it would be // wise to merge those two. return monthScene()->monthView()->preferences()->agendaHolidaysBackgroundColor(); } QColor HolidayMonthItem::frameColor() const { return Qt::black; } diff --git a/calendarviews/month/monthitem.h b/calendarviews/month/monthitem.h index 0af86f3668..efc49468ee 100644 --- a/calendarviews/month/monthitem.h +++ b/calendarviews/month/monthitem.h @@ -1,374 +1,372 @@ /* Copyright (c) 2008 Bruno Virlet This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #ifndef EVENTVIEWS_MONTHITEM_H #define EVENTVIEWS_MONTHITEM_H #include "eventviews_export.h" #include #include -#include +#include "viewcalendar.h" #include #include namespace EventViews { class MonthGraphicsItem; class MonthScene; /** * A month item manages different MonthGraphicsItems. */ class EVENTVIEWS_EXPORT MonthItem : public QObject { Q_OBJECT public: explicit MonthItem( MonthScene *monthWidget ); virtual ~MonthItem(); QWidget *parentWidget() const; /** Compares two events The month view displays a list of items. When loading (which occurs each time there is a change), the items are sorted : - smallest date - bigger span - floating - finally, time in the day */ static bool greaterThan( const MonthItem *e1, const MonthItem *e2 ); /** Compare this event with a second one, if the former function is not able to sort them. */ virtual bool greaterThanFallback( const MonthItem *other ) const; /** The start date of the incidence, generally realStartDate. But it reflect changes, even during move. */ QDate startDate() const; /** The end date of the incidence, generally realEndDate. But it reflect changes, even during move. */ QDate endDate() const; /** The number of days this item spans. */ int daySpan() const; /** This is the real start date, usually the start date of the incidence. */ virtual QDate realStartDate() const = 0; /** This is the real end date, usually the end date of the incidence. */ virtual QDate realEndDate() const = 0; /** True if this item last all the day. */ virtual bool allDay() const = 0; /** Updates geometry of all MonthGraphicsItems. */ void updateGeometry(); /** Find the lowest possible position for this item. The position of an item in a cell is it's vertical position. This is used to avoid overlapping of items. An item keeps the same position in every cell it crosses. The position is measured from top to bottom. */ void updatePosition(); /** Returns true if this item is selected. */ bool selected() const { return mSelected; } /** Returns the position of the item ( > 0 ). */ int position() const { return mPosition; } /** Returns the associated month scene to this item. */ MonthScene *monthScene() const { return mMonthScene; } /** Begin a move. */ void beginMove(); /** End a move. */ void endMove(); /** Begin a resize. */ void beginResize(); /** End a resize. */ void endResize(); /** Called during move to move the item a bit, relative to the previous move step. */ void moveBy( int offsetFromPreviousDate ); /** Called during resize to resize the item a bit, relative to the previous resize step. */ bool resizeBy( int offsetFromPreviousDate ); /** Returns true if the item is being moved. */ bool isMoving() const { return mMoving; } /** Returns true if the item is being resized. */ bool isResizing() const { return mResizing; } /** Returns true if the item can be moved. */ virtual bool isMoveable() const = 0; /** Returns true if the item can be resized. */ virtual bool isResizable() const = 0; /** Deletes all MonthGraphicsItem this item handles. Clear the list. */ void deleteAll( ); /** Update the monthgraphicsitems This basically deletes and rebuild all the MonthGraphicsItems but tries to do it wisely: - If there is a moving item, it won't be deleted because then the new item won't receive anymore the MouseMove events. - If there is an item on a line where the new state needs an item, it is used and not deleted. This will avoid flickers. */ void updateMonthGraphicsItems(); /** Sets the selection state of this item. */ void setSelected( bool selected ) { mSelected = selected; } // METHODS NEEDED TO PAINT ITEMS /** Returns the text to draw in an item. @param end True if the text at the end of an item should be returned. */ virtual QString text( bool end ) const = 0; /** Returns the text for the tooltip of the item */ virtual QString toolTipText( const QDate &date ) const = 0; /** Returns the background color of the item. */ virtual QColor bgColor() const = 0; /** Returns the frame color of the item. */ virtual QColor frameColor() const = 0; /** Returns a list of pixmaps to draw next to the items. */ virtual QList icons() const = 0; QList monthGraphicsItems() const; protected: /** Called after a move operation. */ virtual void finalizeMove( const QDate &newStartDate ) = 0; /** Called after a resize operation. */ virtual void finalizeResize( const QDate &newStartDate, const QDate &newEndDate ) = 0; private: /** Sets the value of all MonthGraphicsItem to @param z. */ void setZValue( qreal z ); QList mMonthGraphicsItemList; MonthScene *mMonthScene; bool mSelected; bool mMoving; // during move bool mResizing; // during resize QDate mOverrideStartDate; int mOverrideDaySpan; int mPosition; }; class EVENTVIEWS_EXPORT IncidenceMonthItem : public MonthItem { Q_OBJECT public: IncidenceMonthItem( MonthScene *monthScene, - const Akonadi::ETMCalendar::Ptr &calendar, - const Akonadi::Item &item, + const MultiViewCalendar::Ptr &calendar, const KCalCore::Incidence::Ptr &incidence, const QDate &recurStartDate = QDate() ); virtual ~IncidenceMonthItem(); KCalCore::Incidence::Ptr incidence() const; Akonadi::Item akonadiItem() const; Akonadi::Item::Id akonadiItemId() const; virtual bool greaterThanFallback( const MonthItem *other ) const; virtual QDate realStartDate() const; virtual QDate realEndDate() const; virtual bool allDay() const; virtual bool isMoveable() const; virtual bool isResizable() const; QString text( bool end ) const; QString toolTipText( const QDate &date ) const; QColor bgColor() const; QColor frameColor() const; QList icons() const; protected: virtual void finalizeMove( const QDate &newStartDate ); virtual void finalizeResize( const QDate &newStartDate, const QDate &newEndDate ); protected slots: /** Update the selected state of this item. If will be selected if incidence is the incidence managed by this item. Else it will be deselected. */ void updateSelection( const Akonadi::Item &incidence, const QDate &date ); private: void updateDates( int startOffset, int endOffset ); void setNewDates( const KCalCore::Incidence::Ptr &incidence, int startOffset, int endOffset ); /** Returns the category color for this incidence. */ QColor catColor() const; - Akonadi::ETMCalendar::Ptr mCalendar; + MultiViewCalendar::Ptr mCalendar; KCalCore::Incidence::Ptr mIncidence; - Akonadi::Item::Id mAkonadiItemId; int mRecurDayOffset; bool mIsEvent, mIsTodo, mIsJournal; }; class EVENTVIEWS_EXPORT HolidayMonthItem : public MonthItem { Q_OBJECT public: HolidayMonthItem( MonthScene *monthScene, const QDate &date, const QString &name ); virtual ~HolidayMonthItem(); virtual bool greaterThanFallback( const MonthItem *other ) const; virtual QDate realStartDate() const { return mDate; } virtual QDate realEndDate() const { return mDate; } virtual bool allDay() const { return true; } virtual bool isMoveable() const { return false; } virtual bool isResizable() const { return false; } QString text( bool end ) const { Q_UNUSED( end ); return mName; } QString toolTipText( const QDate & ) const { return mName; } QColor bgColor() const; QColor frameColor() const; QList icons() const; protected: virtual void finalizeMove( const QDate &newStartDate ); virtual void finalizeResize( const QDate &newStartDate, const QDate &newEndDate ); private: QDate mDate; QString mName; }; } #endif diff --git a/calendarviews/month/monthscene.cpp b/calendarviews/month/monthscene.cpp index 05c0677276..012af592ca 100644 --- a/calendarviews/month/monthscene.cpp +++ b/calendarviews/month/monthscene.cpp @@ -1,806 +1,806 @@ /* Copyright (c) 2008 Bruno Virlet This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #include "monthscene.h" #include "monthgraphicsitems.h" #include "monthitem.h" #include "monthview.h" #include "prefs.h" #include #include #include #include #include #include static const int AUTO_REPEAT_DELAY = 600; using namespace EventViews; MonthScene::MonthScene( MonthView *parent ) : QGraphicsScene( parent ), mMonthView( parent ), mInitialized( false ), mClickedItem( 0 ), mActionItem( 0 ), mActionInitiated( false ), mSelectedItem( 0 ), mStartCell( 0 ), mPreviousCell( 0 ), mActionType( None ), mStartHeight( 0 ), mCurrentIndicator( 0 ) { mBirthdayPixmap = SmallIcon( QLatin1String("view-calendar-birthday") ); mAnniversaryPixmap = SmallIcon( QLatin1String("view-calendar-wedding-anniversary") ); mAlarmPixmap = SmallIcon( QLatin1String("appointment-reminder" )); mRecurPixmap = SmallIcon( QLatin1String("appointment-recurring") ); mReadonlyPixmap = SmallIcon( QLatin1String("object-locked") ); mReplyPixmap = SmallIcon( QLatin1String("mail-reply-sender") ); mHolidayPixmap = SmallIcon( QLatin1String("view-calendar-holiday") ); setSceneRect( 0, 0, parent->width(), parent->height() ); } MonthScene::~MonthScene() { qDeleteAll( mMonthCellMap ); qDeleteAll( mManagerList ); } MonthCell *MonthScene::selectedCell() const { return mMonthCellMap.value( mSelectedCellDate ); } MonthCell *MonthScene::previousCell() const { return mPreviousCell; } int MonthScene::getRightSpan( const QDate &date ) const { MonthCell *cell = mMonthCellMap.value( date ); if ( !cell ) { return 0; } return 7 - cell->x() - 1; } int MonthScene::getLeftSpan( const QDate &date ) const { MonthCell *cell = mMonthCellMap.value( date ); if ( !cell ) { return 0; } return cell->x(); } int MonthScene::maxRowCount() { return ( rowHeight() - MonthCell::topMargin() ) / itemHeightIncludingSpacing(); } int MonthScene::itemHeightIncludingSpacing() { return MonthCell::topMargin() + 2; } int MonthScene::itemHeight() { return MonthCell::topMargin(); } MonthCell *MonthScene::firstCellForMonthItem( MonthItem *manager ) { for ( QDate d = manager->startDate(); d <= manager->endDate(); d = d.addDays( 1 ) ) { MonthCell *monthCell = mMonthCellMap.value( d ); if ( monthCell ) { return monthCell; } } return 0; } void MonthScene::updateGeometry() { foreach ( MonthItem *manager, mManagerList ) { manager->updateGeometry(); } } int MonthScene::availableWidth() const { return static_cast ( sceneRect().width() ); } int MonthScene::availableHeight() const { return static_cast ( sceneRect().height() - headerHeight() ); } int MonthScene::columnWidth() const { return static_cast ( ( availableWidth() - 1 ) / 7. ); } int MonthScene::rowHeight() const { return static_cast ( ( availableHeight() - 1 ) / 6. ); } int MonthScene::headerHeight() const { return 50; } int MonthScene::cellVerticalPos( const MonthCell *cell ) const { return headerHeight() + cell->y() * rowHeight(); } int MonthScene::cellHorizontalPos( const MonthCell *cell ) const { return cell->x() * columnWidth(); } int MonthScene::sceneYToMonthGridY( int yScene ) { return yScene - headerHeight(); } int MonthScene::sceneXToMonthGridX( int xScene ) { return xScene; } void MonthGraphicsView::drawBackground( QPainter *p, const QRectF & rect ) { Q_ASSERT( mScene ); PrefsPtr prefs = mScene->monthView()->preferences(); p->setFont( prefs->monthViewFont() ); p->fillRect( rect, palette().color( QPalette::Base ) ); /* Headers */ QFont font = prefs->monthViewFont(); font.setBold( true ); font.setPointSize( 15 ); p->setFont( font ); const int dayLabelsHeight = 20; const KCalendarSystem *calSys = KGlobal::locale()->calendar(); p->drawText( QRect( 0, 0, // top right static_cast ( mScene->sceneRect().width() ), static_cast ( mScene->headerHeight() - dayLabelsHeight ) ), Qt::AlignCenter, i18nc( "monthname year", "%1 %2", calSys->monthName( mMonthView->averageDate() ), calSys->formatDate( mMonthView->averageDate(), KLocale::Year, KLocale::LongNumber ) ) ); font.setPixelSize( dayLabelsHeight - 10 ); p->setFont( font ); const QDate start = mMonthView->actualStartDateTime().date(); const QDate end = mMonthView->actualEndDateTime().date(); for ( QDate d = start; d <= start.addDays( 6 ); d = d.addDays( 1 ) ) { MonthCell *cell = mScene->mMonthCellMap.value( d ); if ( !cell ) { // This means drawBackground() is being called before reloadIncidences(). Can happen with some // themes. Bug #190191 return; } p->drawText( QRect( mScene->cellHorizontalPos( cell ), mScene->cellVerticalPos( cell ) - 15, mScene->columnWidth(), 15 ), Qt::AlignCenter, calSys->weekDayName( d, KCalendarSystem::LongDayName ) ); } /* Month grid */ int columnWidth = mScene->columnWidth(); int rowHeight = mScene->rowHeight(); const QList workDays = CalendarSupport::workDays( mMonthView->actualStartDateTime().date(), mMonthView->actualEndDateTime().date() ); for ( QDate d = start; d <= end; d = d.addDays( 1 ) ) { if ( !mScene->mMonthCellMap.contains( d ) ) { // This means drawBackground() is being called before reloadIncidences(). Can happen with some // themes. Bug #190191 return; } MonthCell *cell = mScene->mMonthCellMap[ d ]; QColor color; if ( workDays.contains( d ) ) { color = mMonthView->preferences()->monthGridWorkHoursBackgroundColor(); } else { color = mMonthView->preferences()->monthGridBackgroundColor(); } if ( cell == mScene->selectedCell() ) { color = color.dark( 115 ); } if ( cell->date() == QDate::currentDate() ) { color = color.dark( 140 ); } // Draw cell p->setPen( mMonthView->preferences()->monthGridBackgroundColor().dark( 150 ) ); p->setBrush( color ); p->drawRect( QRect( mScene->cellHorizontalPos( cell ), mScene->cellVerticalPos( cell ), columnWidth, rowHeight ) ); if ( mMonthView->isBusyDay( d ) ) { QColor busyColor = mMonthView->preferences()->viewBgBusyColor(); busyColor.setAlpha( EventViews::BUSY_BACKGROUND_ALPHA ); p->setBrush( busyColor ); p->drawRect( QRect( mScene->cellHorizontalPos( cell ), mScene->cellVerticalPos( cell ), columnWidth, rowHeight ) ); } // Draw cell header int cellHeaderX = mScene->cellHorizontalPos( cell ) + 1; int cellHeaderY = mScene->cellVerticalPos( cell ) + 1; int cellHeaderWidth = columnWidth - 2; int cellHeaderHeight = cell->topMargin() - 2; QLinearGradient bgGradient( QPointF( cellHeaderX, cellHeaderY ), QPointF( cellHeaderX + cellHeaderWidth, cellHeaderY + cellHeaderHeight ) ); bgGradient.setColorAt( 0, color.dark( 105 ) ); bgGradient.setColorAt( 0.7, color.dark( 105 ) ); bgGradient.setColorAt( 1, color ); p->setBrush( bgGradient ); p->setPen( Qt::NoPen ); p->drawRect( QRect( cellHeaderX, cellHeaderY, cellHeaderWidth, cellHeaderHeight ) ); } font = mMonthView->preferences()->monthViewFont(); font.setPixelSize( MonthCell::topMargin() - 4 ); p->setFont( font ); QPen oldPen = mMonthView->preferences()->monthGridBackgroundColor().dark( 150 ); // Draw dates for ( QDate d = mMonthView->actualStartDateTime().date(); d <= mMonthView->actualEndDateTime().date(); d = d.addDays( 1 ) ) { MonthCell *cell = mScene->mMonthCellMap.value( d ); QFont font = p->font(); if ( cell->date() == QDate::currentDate() ) { font.setBold( true ); } else { font.setBold( false ); } p->setFont( font ); if ( d.month() == mMonthView->currentMonth() ) { p->setPen( QPalette::Text ); } else { p->setPen( oldPen ); } /* Draw arrows if all items won't fit */ // Up arrow if first item is above cell top if ( mScene->startHeight() != 0 && cell->hasEventBelow( mScene->startHeight() ) ) { cell->upArrow()->setPos( mScene->cellHorizontalPos( cell ) + columnWidth / 2, mScene->cellVerticalPos( cell ) + cell->upArrow()->boundingRect().height() / 2 + 2 ); cell->upArrow()->show(); } else { cell->upArrow()->hide(); } // Down arrow if last item is below cell bottom if ( !mScene->lastItemFit( cell ) ) { cell->downArrow()->setPos( mScene->cellHorizontalPos( cell ) + columnWidth / 2, mScene->cellVerticalPos( cell ) + rowHeight - cell->downArrow()->boundingRect().height() / 2 - 2 ); cell->downArrow()->show(); } else { cell->downArrow()->hide(); } const KCalendarSystem *calSys = KGlobal::locale()->calendar(); QString dayText; // Prepend month name if d is the first or last day of month if ( calSys->day( d ) == 1 || // d is the first day of month calSys->day( d.addDays( 1 ) ) == 1 ) { // d is the last day of month dayText = i18nc( "'Month day' for month view cells", "%1 %2", calSys->monthName( d, KCalendarSystem::ShortName ), calSys->day( d ) ); } else { dayText = QString::number( calSys->day( d ) ); } p->drawText( QRect( mScene->cellHorizontalPos( cell ), // top right mScene->cellVerticalPos( cell ), // of the cell mScene->columnWidth() - 2, cell->topMargin() ), Qt::AlignRight, dayText ); } // ... } void MonthScene::resetAll() { qDeleteAll( mMonthCellMap ); mMonthCellMap.clear(); qDeleteAll( mManagerList ); mManagerList.clear(); mSelectedItem = 0; mActionItem = 0; mClickedItem = 0; } Akonadi::IncidenceChanger *MonthScene::incidenceChanger() const { return mMonthView->changer(); } QDate MonthScene::firstDateOnRow( int row ) const { return mMonthView->actualStartDateTime().date().addDays( 7 * row ); } bool MonthScene::lastItemFit( MonthCell *cell ) { if ( cell->firstFreeSpace() > maxRowCount() + startHeight() ) { return false; } else { return true; } } int MonthScene::totalHeight() { int max = 0; for ( QDate d = mMonthView->actualStartDateTime().date(); d <= mMonthView->actualEndDateTime().date(); d = d.addDays( 1 ) ) { int c = mMonthCellMap[ d ]->firstFreeSpace(); if ( c > max ) { max = c; } } return max; } void MonthScene::wheelEvent( QGraphicsSceneWheelEvent *event ) { Q_UNUSED( event ); // until we figure out what to do in here /* int numDegrees = -event->delta() / 8; int numSteps = numDegrees / 15; if ( startHeight() + numSteps < 0 ) { numSteps = -startHeight(); } int cellHeight = 0; MonthCell *currentCell = getCellFromPos( event->scenePos() ); if ( currentCell ) { cellHeight = currentCell->firstFreeSpace(); } if ( cellHeight == 0 ) { // no items in this cell, there's no point to scroll return; } int newHeight; int maxStartHeight = qMax( 0, cellHeight - maxRowCount() ); if ( numSteps > 0 && startHeight() + numSteps >= maxStartHeight ) { newHeight = maxStartHeight; } else { newHeight = startHeight() + numSteps; } if ( newHeight == startHeight() ) { return; } setStartHeight( newHeight ); foreach ( MonthItem *manager, mManagerList ) { manager->updateGeometry(); } invalidate( QRectF(), BackgroundLayer ); event->accept(); */ } void MonthScene::scrollCellsDown() { int newHeight = startHeight() + 1; setStartHeight( newHeight ); foreach ( MonthItem *manager, mManagerList ) { manager->updateGeometry(); } invalidate( QRectF(), BackgroundLayer ); } void MonthScene::scrollCellsUp() { int newHeight = startHeight() - 1; setStartHeight( newHeight ); foreach ( MonthItem *manager, mManagerList ) { manager->updateGeometry(); } invalidate( QRectF(), BackgroundLayer ); } void MonthScene::clickOnScrollIndicator( ScrollIndicator *scrollItem ) { if ( scrollItem->direction() == ScrollIndicator::UpArrow ) { scrollCellsUp(); } else if ( scrollItem->direction() == ScrollIndicator::DownArrow ) { scrollCellsDown(); } } void MonthScene::mouseDoubleClickEvent ( QGraphicsSceneMouseEvent *mouseEvent ) { QPointF pos = mouseEvent->scenePos(); repeatTimer.stop(); MonthGraphicsItem *iItem = dynamic_cast( itemAt( pos ) ); if ( iItem ) { if ( iItem->monthItem() ) { IncidenceMonthItem *tmp = qobject_cast( iItem->monthItem() ); if ( tmp ) { selectItem( iItem->monthItem() ); mMonthView->defaultAction( tmp->akonadiItem() ); mouseEvent->accept(); } } } else { emit newEventSignal(); } } void MonthScene::mouseMoveEvent ( QGraphicsSceneMouseEvent *mouseEvent ) { QPointF pos = mouseEvent->scenePos(); MonthGraphicsView *view = static_cast( views().first() ); // Change cursor depending on the part of the item it hovers to inform // the user that he can resize the item. if ( mActionType == None ) { MonthGraphicsItem *iItem = dynamic_cast( itemAt( pos ) ); if ( iItem ) { if ( iItem->monthItem()->isResizable() && iItem->isBeginItem() && iItem->mapFromScene( pos ).x() <= 10 ) { view->setActionCursor( Resize ); } else if ( iItem->monthItem()->isResizable() && iItem->isEndItem() && iItem->mapFromScene( pos ).x() >= iItem->boundingRect().width() - 10 ) { view->setActionCursor( Resize ); } else { view->setActionCursor( None ); } } else { view->setActionCursor( None ); } mouseEvent->accept(); return; } // If an item was selected during the click, we maybe have an item to move ! if ( mActionItem ) { MonthCell *currentCell = getCellFromPos( pos ); // Initiate action if not already done if ( !mActionInitiated && mActionType != None ) { if ( mActionType == Move ) { mActionItem->beginMove(); } else if ( mActionType == Resize ) { mActionItem->beginResize(); } mActionInitiated = true; } view->setActionCursor( mActionType ); // Move or resize action if ( currentCell && currentCell != mPreviousCell ) { bool ok = true; if ( mActionType == Move ) { mActionItem->moveBy( mPreviousCell->date().daysTo( currentCell->date() ) ); } else if ( mActionType == Resize ) { ok = mActionItem->resizeBy( mPreviousCell->date().daysTo( currentCell->date() ) ); } if ( ok ) { mPreviousCell = currentCell; } mActionItem->updateGeometry(); update(); } mouseEvent->accept(); } } void MonthScene::mousePressEvent ( QGraphicsSceneMouseEvent *mouseEvent ) { QPointF pos = mouseEvent->scenePos(); mClickedItem = 0; mCurrentIndicator = 0; MonthGraphicsItem *iItem = dynamic_cast( itemAt( pos ) ); if ( iItem ) { mClickedItem = iItem->monthItem(); selectItem( mClickedItem ); if ( mouseEvent->button() == Qt::RightButton ) { IncidenceMonthItem *tmp = qobject_cast( mClickedItem ); if ( tmp ) { emit showIncidencePopupSignal( tmp->akonadiItem(), tmp->realStartDate() ); } } if ( mouseEvent->button() == Qt::LeftButton ) { // Basic initialization for resize and move mActionItem = mClickedItem; mStartCell = getCellFromPos( pos ); mPreviousCell = mStartCell; mActionInitiated = false; // Move or resize ? if ( iItem->monthItem()->isResizable() && iItem->isBeginItem() && iItem->mapFromScene( pos ).x() <= 10 ) { mActionType = Resize; mResizeType = ResizeLeft; } else if ( iItem->monthItem()->isResizable() && iItem->isEndItem() && iItem->mapFromScene( pos ).x() >= iItem->boundingRect().width() - 10 ) { mActionType = Resize; mResizeType = ResizeRight; } else if ( iItem->monthItem()->isMoveable() ) { mActionType = Move; } } mouseEvent->accept(); } else if ( ScrollIndicator *scrollItem = dynamic_cast( itemAt( pos ) ) ) { clickOnScrollIndicator( scrollItem ); mCurrentIndicator = scrollItem; repeatTimer.start( AUTO_REPEAT_DELAY, this ); } else { // unselect items when clicking somewhere else selectItem( 0 ); MonthCell *cell = getCellFromPos( pos ); if ( cell ) { mSelectedCellDate = cell->date(); update(); if ( mouseEvent->button() == Qt::RightButton ) { emit showNewEventPopupSignal(); } mouseEvent->accept(); } } } void MonthScene::timerEvent( QTimerEvent *e ) { if ( e->timerId() == repeatTimer.timerId() ) { if ( mCurrentIndicator->isVisible() ) { clickOnScrollIndicator( mCurrentIndicator ); repeatTimer.start( AUTO_REPEAT_DELAY, this ); } else { mCurrentIndicator = 0; repeatTimer.stop(); } } } void MonthScene::helpEvent( QGraphicsSceneHelpEvent *helpEvent ) { // Find the first item that does tooltips const QPointF pos = helpEvent->scenePos(); MonthGraphicsItem *toolTipItem = dynamic_cast( itemAt( pos ) ); // Show or hide the tooltip QString text; QPoint point; if (toolTipItem) { text = toolTipItem->getToolTip(); point = helpEvent->screenPos(); } QToolTip::showText(point, text, helpEvent->widget()); helpEvent->setAccepted(!text.isEmpty()); } void MonthScene::mouseReleaseEvent ( QGraphicsSceneMouseEvent *mouseEvent ) { QPointF pos = mouseEvent->scenePos(); static_cast( views().first() )->setActionCursor( None ); repeatTimer.stop(); mCurrentIndicator = 0; if ( mActionItem ) { MonthCell *currentCell = getCellFromPos( pos ); const bool somethingChanged = currentCell && currentCell != mStartCell; if ( somethingChanged ) { // We want to act if a move really happened if ( mActionType == Resize ) { mActionItem->endResize(); } else if ( mActionType == Move ) { mActionItem->endMove(); } } mActionItem = 0; mActionType = None; mStartCell = 0; mouseEvent->accept(); } } // returns true if the point is in the monthgrid (allows to avoid selecting a cell when // a click is outside the month grid bool MonthScene::isInMonthGrid( int x, int y ) const { return x >= 0 && y >= 0 && x <= availableWidth() && y <= availableHeight(); } // The function converts the coordinates to the month grid coordinates to // be able to locate the cell. MonthCell *MonthScene::getCellFromPos( const QPointF &pos ) { int y = sceneYToMonthGridY( static_cast ( pos.y() ) ); int x = sceneXToMonthGridX( static_cast ( pos.x() ) ); if ( !isInMonthGrid( x, y ) ) { return 0; } int id = ( int )( y / rowHeight() ) * 7 + ( int )( x / columnWidth() ); return mMonthCellMap.value( mMonthView->actualStartDateTime().date().addDays( id ) ); } void MonthScene::selectItem( MonthItem *item ) { /* if ( mSelectedItem == item ) { return; } I commented the above code so it's possible to selected a selected item. korg-mobile needs that, otherwise clicking on a selected item wont bring the editor up. Another solution would be to have two signals: incidenceSelected() and incidenceClicked() */ IncidenceMonthItem *tmp = qobject_cast( item ); if ( !tmp ) { mSelectedItem = 0; emit incidenceSelected( Akonadi::Item(), QDate() ); return; } mSelectedItem = item; Q_ASSERT( CalendarSupport::hasIncidence( tmp->akonadiItem() ) ); if ( mMonthView->selectedIncidenceDates().isEmpty() ) { emit incidenceSelected( tmp->akonadiItem(), QDate() ); } else { emit incidenceSelected( tmp->akonadiItem(), mMonthView->selectedIncidenceDates().first() ); } update(); } void MonthScene::removeIncidence( const QString &uid ) { foreach ( MonthItem *manager, mManagerList ) { IncidenceMonthItem *imi = qobject_cast( manager ); if ( !imi ) continue; KCalCore::Incidence::Ptr incidence = imi->incidence(); if ( !incidence ) continue; - if ( incidence->uid() == uid ) { + if ( mMonthView->viewCalendar()->uid(incidence) == uid ) { foreach ( MonthGraphicsItem *gitem, imi->monthGraphicsItems() ) { removeItem( gitem ); } } } } //---------------------------------------------------------- MonthGraphicsView::MonthGraphicsView( MonthView *parent ) : QGraphicsView( parent ), mMonthView( parent ) { setMouseTracking( true ); } void MonthGraphicsView::setActionCursor( MonthScene::ActionType actionType ) { switch ( actionType ) { case MonthScene::Move: #ifndef QT_NO_CURSOR setCursor( Qt::ArrowCursor ); #endif break; case MonthScene::Resize: #ifndef QT_NO_CURSOR setCursor( Qt::SizeHorCursor ); #endif break; #ifndef QT_NO_CURSOR default: setCursor( Qt::ArrowCursor ); #endif } } void MonthGraphicsView::setScene( MonthScene *scene ) { mScene = scene; QGraphicsView::setScene( scene ); } void MonthGraphicsView::resizeEvent( QResizeEvent *event ) { mScene->setSceneRect( 0, 0, event->size().width(), event->size().height() ); mScene->updateGeometry(); } diff --git a/calendarviews/month/monthview.cpp b/calendarviews/month/monthview.cpp index 5b7c1b1e70..9bef2a66b3 100644 --- a/calendarviews/month/monthview.cpp +++ b/calendarviews/month/monthview.cpp @@ -1,638 +1,637 @@ /* Copyright (c) 2008 Bruno Virlet Copyright (C) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net Author: Bertjan Broeksema, broeksema@kde.org This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #include "monthview.h" #include "monthgraphicsitems.h" #include "monthitem.h" #include "monthscene.h" #include "prefs.h" #include #include #include #include #include #include #include #include #include #include #include using namespace EventViews; namespace EventViews { class MonthViewPrivate : public Akonadi::ETMCalendar::CalendarObserver { MonthView *q; public: /// Methods explicit MonthViewPrivate( MonthView *qq ); void addIncidence( const Akonadi::Item &incidence ); void moveStartDate( int weeks, int months ); // void setUpModels(); void triggerDelayedReload( EventView::Change reason ); public: /// Members QTimer reloadTimer; MonthScene *scene; QDate selectedItemDate; Akonadi::Item::Id selectedItemId; MonthGraphicsView *view; QToolButton *fullView; // List of uids for QDate QMap mBusyDays; protected: /* reimplemented from KCalCore::Calendar::CalendarObserver */ void calendarIncidenceAdded( const KCalCore::Incidence::Ptr &incidence ); void calendarIncidenceChanged( const KCalCore::Incidence::Ptr &incidence ); void calendarIncidenceDeleted( const KCalCore::Incidence::Ptr &incidence ); }; } MonthViewPrivate::MonthViewPrivate( MonthView *qq ) : q( qq ), scene( new MonthScene( qq ) ), selectedItemId( -1 ), view( new MonthGraphicsView( qq ) ), fullView( 0 ) { reloadTimer.setSingleShot( true ); view->setScene( scene ); } void MonthViewPrivate::addIncidence( const Akonadi::Item &incidence ) { Q_UNUSED( incidence ); //TODO: add some more intelligence here... q->setChanges( q->changes() | EventView::IncidencesAdded ); reloadTimer.start( 50 ); } void MonthViewPrivate::moveStartDate( int weeks, int months ) { KDateTime start = q->startDateTime(); KDateTime end = q->endDateTime(); start = start.addDays( weeks * 7 ); end = end.addDays( weeks * 7 ); start = start.addMonths( months ); end = end.addMonths( months ); #ifndef KDEPIM_MOBILE_UI KCalCore::DateList dateList; QDate d = start.date(); while ( d <= end.date() ) { dateList.append( d ); d = d.addDays( 1 ); } /** * If we call q->setDateRange( start, end ); directly, * it will change the selected dates in month view, * but the application won't know about it. * The correct way is to emit datesSelected() * #250256 * */ emit q->datesSelected( dateList ); #else // korg-mobile doesn't use korg's date navigator. // Before creating a solution with no #ifndef, we must first extract the remaining views from // korg, and review the API. q->setDateRange( start, end ); #endif } void MonthViewPrivate::triggerDelayedReload( EventView::Change reason ) { q->setChanges( q->changes() | reason ); if ( !reloadTimer.isActive() ) { reloadTimer.start( 50 ); } } void MonthViewPrivate::calendarIncidenceAdded( const KCalCore::Incidence::Ptr & ) { triggerDelayedReload( MonthView::IncidencesAdded ); } void MonthViewPrivate::calendarIncidenceChanged( const KCalCore::Incidence::Ptr & ) { triggerDelayedReload( MonthView::IncidencesEdited ); } void MonthViewPrivate::calendarIncidenceDeleted( const KCalCore::Incidence::Ptr &incidence ) { Q_ASSERT( !incidence->uid().isEmpty() ); - scene->removeIncidence( incidence->uid() ); + scene->removeIncidence( q->viewCalendar()->uid(incidence) ); } /// MonthView MonthView::MonthView( NavButtonsVisibility visibility, QWidget *parent ) : EventView( parent ), d( new MonthViewPrivate( this ) ) { QHBoxLayout *topLayout = new QHBoxLayout( this ); topLayout->addWidget( d->view ); topLayout->setMargin( 0 ); if ( visibility == Visible ) { QVBoxLayout *rightLayout = new QVBoxLayout( ); rightLayout->setSpacing( 0 ); rightLayout->setMargin( 0 ); // push buttons to the bottom rightLayout->addStretch( 1 ); d->fullView = new QToolButton( this ); d->fullView->setIcon( KIcon( QLatin1String("view-fullscreen") ) ); d->fullView->setAutoRaise( true ); d->fullView->setCheckable( true ); d->fullView->setChecked( preferences()->fullViewMonth() ); d->fullView->isChecked() ? d->fullView->setToolTip( i18nc( "@info:tooltip", "Display calendar in a normal size" ) ) : d->fullView->setToolTip( i18nc( "@info:tooltip", "Display calendar in a full window" ) ); d->fullView->setWhatsThis( i18nc( "@info:whatsthis", "Click this button and the month view will be enlarged to fill the " "maximum available window space / or shrunk back to its normal size." ) ); connect( d->fullView, SIGNAL(clicked()), this, SLOT(changeFullView()) ); QToolButton *minusMonth = new QToolButton( this ); minusMonth->setIcon( KIcon( QLatin1String("arrow-up-double") ) ); minusMonth->setAutoRaise( true ); minusMonth->setToolTip( i18nc( "@info:tooltip", "Go back one month" ) ); minusMonth->setWhatsThis( i18nc( "@info:whatsthis", "Click this button and the view will be scrolled back in time by 1 month." ) ); connect( minusMonth, SIGNAL(clicked()), this, SLOT(moveBackMonth()) ); QToolButton *minusWeek = new QToolButton( this ); minusWeek->setIcon( KIcon( QLatin1String("arrow-up") ) ); minusWeek->setAutoRaise( true ); minusWeek->setToolTip( i18nc( "@info:tooltip", "Go back one week" ) ); minusWeek->setWhatsThis( i18nc( "@info:whatsthis", "Click this button and the view will be scrolled back in time by 1 week." ) ); connect( minusWeek, SIGNAL(clicked()), this, SLOT(moveBackWeek()) ); QToolButton *plusWeek = new QToolButton( this ); plusWeek->setIcon( KIcon( QLatin1String("arrow-down") ) ); plusWeek->setAutoRaise( true ); plusWeek->setToolTip( i18nc( "@info:tooltip", "Go forward one week" ) ); plusWeek->setWhatsThis( i18nc( "@info:whatsthis", "Click this button and the view will be scrolled forward in time by 1 week." ) ); connect( plusWeek, SIGNAL(clicked()), this, SLOT(moveFwdWeek()) ); QToolButton *plusMonth = new QToolButton( this ); plusMonth->setIcon( KIcon( QLatin1String("arrow-down-double") ) ); plusMonth->setAutoRaise( true ); plusMonth->setToolTip( i18nc( "@info:tooltip", "Go forward one month" ) ); plusMonth->setWhatsThis( i18nc( "@info:whatsthis", "Click this button and the view will be scrolled forward in time by 1 month." ) ); connect( plusMonth, SIGNAL(clicked()), this, SLOT(moveFwdMonth()) ); rightLayout->addWidget( d->fullView ); rightLayout->addWidget( minusMonth ); rightLayout->addWidget( minusWeek ); rightLayout->addWidget( plusWeek ); rightLayout->addWidget( plusMonth ); topLayout->addLayout( rightLayout ); } else { d->view->setFrameStyle( QFrame::NoFrame ); } connect( d->scene, SIGNAL(showIncidencePopupSignal(Akonadi::Item,QDate)), SIGNAL(showIncidencePopupSignal(Akonadi::Item,QDate)) ); connect( d->scene, SIGNAL(incidenceSelected(Akonadi::Item,QDate)), SIGNAL(incidenceSelected(Akonadi::Item,QDate)) ); connect( d->scene, SIGNAL(newEventSignal()), SIGNAL(newEventSignal()) ); connect( d->scene, SIGNAL(showNewEventPopupSignal()), SIGNAL(showNewEventPopupSignal()) ); connect( &d->reloadTimer, SIGNAL(timeout()), this, SLOT(reloadIncidences()) ); updateConfig(); // d->setUpModels(); d->reloadTimer.start( 50 ); } MonthView::~MonthView() { - if ( calendar() ) { - calendar()->unregisterObserver( d ); + if ( etmCalendar() ) { + etmCalendar()->unregisterObserver( d ); } delete d; } void MonthView::updateConfig() { d->scene->update(); setChanges( changes() | ConfigChanged ); d->reloadTimer.start( 50 ); } int MonthView::currentDateCount() const { return actualStartDateTime().date().daysTo( actualEndDateTime().date() ); } KCalCore::DateList MonthView::selectedIncidenceDates() const { KCalCore::DateList list; if ( d->scene->selectedItem() ) { IncidenceMonthItem *tmp = qobject_cast( d->scene->selectedItem() ); if ( tmp ) { QDate selectedItemDate = tmp->realStartDate(); if ( selectedItemDate.isValid() ) { list << selectedItemDate; } } } else if ( d->scene->selectedCell() ) { list << d->scene->selectedCell()->date(); } return list; } QDateTime MonthView::selectionStart() const { if ( d->scene->selectedCell() ) { return QDateTime( d->scene->selectedCell()->date() ); } else { return QDateTime(); } } QDateTime MonthView::selectionEnd() const { // Only one cell can be selected (for now) return selectionStart(); } void MonthView::setDateRange( const KDateTime &start, const KDateTime &end, const QDate &preferredMonth ) { EventView::setDateRange( start, end, preferredMonth ); setChanges( changes() | DatesChanged ); d->reloadTimer.start( 50 ); } bool MonthView::eventDurationHint( QDateTime &startDt, QDateTime &endDt, bool &allDay ) const { if ( d->scene->selectedCell() ) { startDt.setDate( d->scene->selectedCell()->date() ); endDt.setDate( d->scene->selectedCell()->date() ); allDay = true; return true; } return false; } void MonthView::showIncidences( const Akonadi::Item::List &incidenceList, const QDate &date ) { Q_UNUSED( incidenceList ); Q_UNUSED( date ); } void MonthView::changeIncidenceDisplay( const Akonadi::Item &incidence, int action ) { Q_UNUSED( incidence ); Q_UNUSED( action ); //TODO: add some more intelligence here... // don't call reloadIncidences() directly. It would delete all // MonthItems, but this changeIncidenceDisplay()-method was probably // called by one of the MonthItem objects. So only schedule a reload // as event setChanges( changes() | IncidencesEdited ); d->reloadTimer.start( 50 ); } void MonthView::updateView() { d->view->update(); } #ifndef QT_NO_WHEELEVENT void MonthView::wheelEvent( QWheelEvent *event ) { // invert direction to get scroll-like behaviour if ( event->delta() > 0 ) { d->moveStartDate( -1, 0 ); } else if ( event->delta() < 0 ) { d->moveStartDate( 1, 0 ); } // call accept in every case, we do not want anybody else to react event->accept(); } #endif void MonthView::keyPressEvent( QKeyEvent *event ) { if ( event->key() == Qt::Key_PageUp ) { d->moveStartDate( 0, -1 ); event->accept(); } else if ( event->key() == Qt::Key_PageDown ) { d->moveStartDate( 0, 1 ); event->accept(); } else if ( processKeyEvent( event ) ) { event->accept(); } else { event->ignore(); } } void MonthView::keyReleaseEvent( QKeyEvent *event ) { if ( processKeyEvent( event ) ) { event->accept(); } else { event->ignore(); } } void MonthView::changeFullView() { bool fullView = d->fullView->isChecked(); if( fullView ) { d->fullView->setIcon( KIcon( QLatin1String("view-restore") ) ); d->fullView->setToolTip( i18nc( "@info:tooltip", "Display calendar in a normal size" ) ); } else { d->fullView->setIcon( KIcon( QLatin1String("view-fullscreen") ) ); d->fullView->setToolTip( i18nc( "@info:tooltip", "Display calendar in a full window" ) ); } preferences()->setFullViewMonth( fullView ); preferences()->writeConfig(); emit fullViewChanged( fullView ); } void MonthView::moveBackMonth() { d->moveStartDate( 0, -1 ); } void MonthView::moveBackWeek() { d->moveStartDate( -1, 0 ); } void MonthView::moveFwdWeek() { d->moveStartDate( 1, 0 ); } void MonthView::moveFwdMonth() { d->moveStartDate( 0, 1 ); } void MonthView::showDates( const QDate &start, const QDate &end, const QDate &preferedMonth ) { Q_UNUSED( start ); Q_UNUSED( end ); Q_UNUSED( preferedMonth ); d->triggerDelayedReload( DatesChanged ); } QPair MonthView::actualDateRange( const KDateTime &start, const KDateTime &, const QDate &preferredMonth ) const { KDateTime dayOne = preferredMonth.isValid() ? KDateTime( preferredMonth ) : start; dayOne.setDate( QDate( dayOne.date().year(), dayOne.date().month(), 1 ) ); const int weekdayCol = ( dayOne.date().dayOfWeek() + 7 - KGlobal::locale()->weekStartDay() ) % 7; KDateTime actualStart = dayOne.addDays( -weekdayCol ); actualStart.setTime( QTime( 0, 0, 0, 0 ) ); KDateTime actualEnd = actualStart.addDays( 6 * 7 - 1 ); actualEnd.setTime( QTime( 23, 59, 59, 99 ) ); return qMakePair( actualStart, actualEnd ); } Akonadi::Item::List MonthView::selectedIncidences() const { Akonadi::Item::List selected; if ( d->scene->selectedItem() ) { IncidenceMonthItem *tmp = qobject_cast( d->scene->selectedItem() ); if ( tmp ) { Akonadi::Item incidenceSelected = tmp->akonadiItem(); if ( incidenceSelected.isValid() ) { selected.append( incidenceSelected ); } } } return selected; } void MonthView::reloadIncidences() { if ( changes() == NothingChanged ) { return; } // keep selection if it exists Akonadi::Item incidenceSelected; MonthItem *itemToReselect = 0; if ( IncidenceMonthItem *tmp = qobject_cast( d->scene->selectedItem() ) ) { d->selectedItemId = tmp->akonadiItem().id(); d->selectedItemDate = tmp->realStartDate(); if ( !d->selectedItemDate.isValid() ) { return; } } d->scene->resetAll(); d->mBusyDays.clear(); // build monthcells hash int i = 0; for ( QDate date = actualStartDateTime().date(); date <= actualEndDateTime().date(); date = date.addDays( 1 ) ) { d->scene->mMonthCellMap[ date ] = new MonthCell( i, date, d->scene ); i ++; } // build global event list KDateTime::Spec timeSpec = CalendarSupport::KCalPrefs::instance()->timeSpec(); const bool colorMonthBusyDays = preferences()->colorMonthBusyDays(); - KCalCore::OccurrenceIterator occurIter( *calendar(), actualStartDateTime(), actualEndDateTime() ); + KCalCore::OccurrenceIterator occurIter( *etmCalendar(), actualStartDateTime(), actualEndDateTime() ); while ( occurIter.hasNext() ) { occurIter.next(); // Remove the two checks when filtering is done through a proxyModel, when using calendar search if ( !preferences()->showTodosMonthView() && occurIter.incidence()->type() == KCalCore::Incidence::TypeTodo ) { continue; } if ( !preferences()->showJournalsMonthView() && occurIter.incidence()->type() == KCalCore::Incidence::TypeJournal ) { continue; } const bool busyDay = colorMonthBusyDays && makesWholeDayBusy( occurIter.incidence() ); if ( busyDay ) { QStringList &list = d->mBusyDays[occurIter.occurrenceStartDate().date()]; - list.append( occurIter.incidence()->uid() ); + list.append( viewCalendar()->uid(occurIter.incidence()) ); } - const Akonadi::Item item = calendar()->item( occurIter.incidence() ); + const Akonadi::Item item = viewCalendar()->item( occurIter.incidence() ); if ( !item.isValid() ) { continue; } Q_ASSERT(item.isValid()); Q_ASSERT(item.hasPayload()); MonthItem *manager = new IncidenceMonthItem( d->scene, - calendar(), - item, + viewCalendar(), occurIter.incidence(), occurIter.occurrenceStartDate().toTimeSpec( timeSpec ).date() ); d->scene->mManagerList << manager; if ( d->selectedItemId == item.id() && manager->realStartDate() == d->selectedItemDate ) { // only select it outside the loop because we are still creating items itemToReselect = manager; } } if ( itemToReselect ) { d->scene->selectItem( itemToReselect ); } // add holidays const QList workDays = CalendarSupport::workDays( actualStartDateTime().date(), actualEndDateTime().date() ); for ( QDate date = actualStartDateTime().date(); date <= actualEndDateTime().date(); date = date.addDays( 1 ) ) { // Only call CalendarSupport::holiday() if it's not a workDay, saves come cpu cicles. if ( !workDays.contains( date ) ) { QStringList holidays( CalendarSupport::holiday( date ) ); if ( !holidays.isEmpty() ) { MonthItem *holidayItem = new HolidayMonthItem( d->scene, date, holidays.join( i18nc( "@item:intext delimiter for joining holiday names", "," ) ) ); d->scene->mManagerList << holidayItem; } } } // sort it qSort( d->scene->mManagerList.begin(), d->scene->mManagerList.end(), MonthItem::greaterThan ); // build each month's cell event list foreach ( MonthItem *manager, d->scene->mManagerList ) { for ( QDate date = manager->startDate(); date <= manager->endDate(); date = date.addDays( 1 ) ) { MonthCell *cell = d->scene->mMonthCellMap.value( date ); if ( cell ) { cell->mMonthItemList << manager; } } } foreach ( MonthItem *manager, d->scene->mManagerList ) { manager->updateMonthGraphicsItems(); manager->updatePosition(); } foreach ( MonthItem *manager, d->scene->mManagerList ) { manager->updateGeometry(); } d->scene->setInitialized( true ); d->view->update(); d->scene->update(); } void MonthView::calendarReset() { kDebug(); d->triggerDelayedReload( ResourcesChanged ); } QDate MonthView::averageDate() const { return actualStartDateTime().date().addDays( actualStartDateTime().date().daysTo( actualEndDateTime().date() ) / 2 ); } int MonthView::currentMonth() const { return averageDate().month(); } bool MonthView::usesFullWindow() { return preferences()->fullViewMonth(); } bool MonthView::isBusyDay( const QDate &day ) const { return !d->mBusyDays[day].isEmpty(); } void MonthView::setCalendar( const Akonadi::ETMCalendar::Ptr &cal ) { Q_ASSERT( cal ); - if ( calendar() ) { - calendar()->unregisterObserver( d ); + if ( etmCalendar() ) { + etmCalendar()->unregisterObserver( d ); } EventView::setCalendar( cal ); - calendar()->registerObserver( d ); + cal->registerObserver( d ); } diff --git a/calendarviews/multiagenda/multiagendaview.cpp b/calendarviews/multiagenda/multiagendaview.cpp index d248d8212e..bf2149bac7 100644 --- a/calendarviews/multiagenda/multiagendaview.cpp +++ b/calendarviews/multiagenda/multiagendaview.cpp @@ -1,790 +1,790 @@ /* Copyright (c) 2007 Volker Krause Copyright (C) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net Author: Sergio Martins This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "multiagendaview.h" #include "configdialoginterface.h" #include "prefs.h" #include "agenda/agenda.h" #include "agenda/agendaview.h" #include "agenda/timelabelszone.h" #include #include #include #include #include #include using namespace Future; #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace Akonadi; using namespace EventViews; /** Function for debugging purposes: prints an object's sizeHint()/minimumSizeHint()/policy and it's children's too, recursively */ /* static void printObject( QObject *o, int level = 0 ) { QMap map; map.insert( 0, "Fixed" ); map.insert( 1, "Minimum" ); map.insert( 4, "Maximum" ); map.insert( 5, "Preferred" ); map.insert( 7, "Expanding" ); map.insert( 3, "MinimumExpaning" ); map.insert( 13, "Ignored" ); QWidget *w = qobject_cast( o ); if ( w ) { qDebug() << QString( level*2, '-' ) << o << w->sizeHint() << "/" << map[w->sizePolicy().verticalPolicy()] << "; minimumSize = " << w->minimumSize() << "; minimumSizeHint = " << w->minimumSizeHint(); } else { qDebug() << QString( level*2, '-' ) << o ; } foreach( QObject *child, o->children() ) { printObject( child, level + 1 ); } } */ static QString generateColumnLabel( int c ) { return i18n( "Agenda %1", c + 1 ); } class MultiAgendaView::Private { public: Private( MultiAgendaView *qq ) : q( qq ), mUpdateOnShow( true ), mPendingChanges( true ), mCustomColumnSetupUsed( false ), mCustomNumberOfColumns( 2 ) { } ~Private() { qDeleteAll( mSelectionSavers ); } void addView( const Akonadi::Collection &collection ); void addView( KCheckableProxyModel *selectionProxy, const QString &title ); AgendaView *createView( const QString &title ); void deleteViews(); void setupViews(); void resizeScrollView( const QSize &size ); MultiAgendaView *q; QList mAgendaViews; QList mAgendaWidgets; KHBox *mTopBox; QScrollArea *mScrollArea; TimeLabelsZone *mTimeLabelsZone; QSplitter *mLeftSplitter, *mRightSplitter; QScrollBar *mScrollBar; QWidget *mLeftBottomSpacer, *mRightBottomSpacer; QDate mStartDate, mEndDate; bool mUpdateOnShow; bool mPendingChanges; bool mCustomColumnSetupUsed; QVector mCollectionSelectionModels; QVector mCustomColumnTitles; int mCustomNumberOfColumns; QLabel *mLabel; QWidget *mRightDummyWidget; QHash* > mSelectionSavers; }; MultiAgendaView::MultiAgendaView( QWidget *parent ) : EventView( parent ), d( new Private( this ) ) { QHBoxLayout *topLevelLayout = new QHBoxLayout( this ); topLevelLayout->setSpacing( 0 ); topLevelLayout->setMargin( 0 ); QFontMetrics fm( font() ); int topLabelHeight = 2 * fm.height() + fm.lineSpacing(); KVBox *topSideBox = new KVBox( this ); QWidget *topSideSpacer = new QWidget( topSideBox ); topSideSpacer->setFixedHeight( topLabelHeight ); d->mLeftSplitter = new QSplitter( Qt::Vertical, topSideBox ); d->mLeftSplitter->setOpaqueResize( KGlobalSettings::opaqueResize() ); d->mLabel = new QLabel( i18n( "All Day" ), d->mLeftSplitter ); d->mLabel->setAlignment( Qt::AlignRight | Qt::AlignVCenter ); d->mLabel->setWordWrap( true ); KVBox *sideBox = new KVBox( d->mLeftSplitter ); // compensate for the frame the agenda views but not the timelabels have QWidget *timeLabelTopAlignmentSpacer = new QWidget( sideBox ); d->mTimeLabelsZone = new TimeLabelsZone( sideBox, PrefsPtr( new Prefs() ) ); QWidget *timeLabelBotAlignmentSpacer = new QWidget( sideBox ); d->mLeftBottomSpacer = new QWidget( topSideBox ); topLevelLayout->addWidget( topSideBox ); d->mScrollArea = new QScrollArea( this ); d->mScrollArea->setWidgetResizable( true ); d->mScrollArea->setVerticalScrollBarPolicy( Qt::ScrollBarAlwaysOff ); // BUG: timelabels aren't aligned with the agenda's grid, 2 or 3 pixels of offset. // asymetric since the timelabels timeLabelTopAlignmentSpacer->setFixedHeight( d->mScrollArea->frameWidth() - 1 ); // have 25 horizontal lines timeLabelBotAlignmentSpacer->setFixedHeight( d->mScrollArea->frameWidth() - 2 ); d->mScrollArea->setFrameShape( QFrame::NoFrame ); topLevelLayout->addWidget( d->mScrollArea, 100 ); d->mTopBox = new KHBox( d->mScrollArea->viewport() ); d->mScrollArea->setWidget( d->mTopBox ); topSideBox = new KVBox( this ); topSideSpacer = new QWidget( topSideBox ); topSideSpacer->setFixedHeight( topLabelHeight ); d->mRightSplitter = new QSplitter( Qt::Vertical, topSideBox ); d->mRightSplitter->setOpaqueResize( KGlobalSettings::opaqueResize() ); connect( d->mLeftSplitter, SIGNAL(splitterMoved(int,int)), SLOT(resizeSplitters()) ); connect( d->mRightSplitter, SIGNAL(splitterMoved(int,int)), SLOT(resizeSplitters()) ); d->mRightDummyWidget = new QWidget( d->mRightSplitter ); d->mScrollBar = new QScrollBar( Qt::Vertical, d->mRightSplitter ); d->mRightBottomSpacer = new QWidget( topSideBox ); topLevelLayout->addWidget( topSideBox ); } void MultiAgendaView::setCalendar( const Akonadi::ETMCalendar::Ptr &calendar ) { EventView::setCalendar( calendar ); Q_FOREACH ( KCheckableProxyModel *proxy, d->mCollectionSelectionModels ) { proxy->setSourceModel( calendar->entityTreeModel() ); } disconnect( 0, SIGNAL(selectionChanged(Akonadi::Collection::List,Akonadi::Collection::List)), this, SLOT(forceRecreateViews())); connect( collectionSelection(), SIGNAL(selectionChanged(Akonadi::Collection::List,Akonadi::Collection::List)), SLOT(forceRecreateViews()) ); recreateViews(); } void MultiAgendaView::recreateViews() { if ( !d->mPendingChanges ) { return; } d->mPendingChanges = false; d->deleteViews(); if ( d->mCustomColumnSetupUsed ) { Q_ASSERT( d->mCollectionSelectionModels.size() == d->mCustomNumberOfColumns ); for ( int i = 0; i < d->mCustomNumberOfColumns; ++i ) { d->addView( d->mCollectionSelectionModels[i], d->mCustomColumnTitles[i] ); } } else { Q_FOREACH ( const Akonadi::Collection &i, collectionSelection()->selectedCollections() ) { if ( i.contentMimeTypes().contains( KCalCore::Event::eventMimeType() ) ) { d->addView( i ); } } } // no resources activated, so stop here to avoid crashing somewhere down the line // TODO: show a nice message instead if ( d->mAgendaViews.isEmpty() ) { return; } d->setupViews(); QTimer::singleShot( 0, this, SLOT(slotResizeScrollView()) ); d->mTimeLabelsZone->updateAll(); QScrollArea *timeLabel = d->mTimeLabelsZone->timeLabels().first(); connect( timeLabel->verticalScrollBar(), SIGNAL(valueChanged(int)), d->mScrollBar, SLOT(setValue(int)) ); connect( d->mScrollBar, SIGNAL(valueChanged(int)), timeLabel->verticalScrollBar(), SLOT(setValue(int)) ); resizeSplitters(); QTimer::singleShot( 0, this, SLOT(setupScrollBar()) ); d->mTimeLabelsZone->updateTimeLabelsPosition(); } void MultiAgendaView::forceRecreateViews() { d->mPendingChanges = true; recreateViews(); } void MultiAgendaView::Private::deleteViews() { Q_FOREACH ( AgendaView *const i, mAgendaViews ) { KCheckableProxyModel *proxy = i->takeCustomCollectionSelectionProxyModel(); if ( proxy && !mCollectionSelectionModels.contains( proxy ) ) { delete proxy; } delete i; } mAgendaViews.clear(); mTimeLabelsZone->setAgendaView( 0 ); qDeleteAll( mAgendaWidgets ); mAgendaWidgets.clear(); } void MultiAgendaView::Private::setupViews() { foreach ( AgendaView *agenda, mAgendaViews ) { q->connect( agenda, SIGNAL(newEventSignal()), q, SIGNAL(newEventSignal()) ); q->connect( agenda, SIGNAL(newEventSignal(QDate)), q, SIGNAL(newEventSignal(QDate)) ); q->connect( agenda, SIGNAL(newEventSignal(QDateTime)), q, SIGNAL(newEventSignal(QDateTime)) ); q->connect( agenda, SIGNAL(newEventSignal(QDateTime,QDateTime)), q, SIGNAL(newEventSignal(QDateTime,QDateTime)) ); q->connect( agenda, SIGNAL(editIncidenceSignal(Akonadi::Item, KDateTime)), q, SIGNAL(editIncidenceSignal(Akonadi::Item, KDateTime)) ); q->connect( agenda, SIGNAL(showIncidenceSignal(Akonadi::Item)), q, SIGNAL(showIncidenceSignal(Akonadi::Item)) ); q->connect( agenda, SIGNAL(deleteIncidenceSignal(Akonadi::Item)), q, SIGNAL(deleteIncidenceSignal(Akonadi::Item)) ); q->connect( agenda, SIGNAL(incidenceSelected(Akonadi::Item,QDate)), q, SIGNAL(incidenceSelected(Akonadi::Item,QDate)) ); q->connect( agenda, SIGNAL(cutIncidenceSignal(Akonadi::Item)), q, SIGNAL(cutIncidenceSignal(Akonadi::Item)) ); q->connect( agenda, SIGNAL(copyIncidenceSignal(Akonadi::Item)), q, SIGNAL(copyIncidenceSignal(Akonadi::Item)) ); q->connect( agenda, SIGNAL(pasteIncidenceSignal()), q, SIGNAL(pasteIncidenceSignal()) ); q->connect( agenda, SIGNAL(toggleAlarmSignal(Akonadi::Item)), q, SIGNAL(toggleAlarmSignal(Akonadi::Item)) ); q->connect( agenda, SIGNAL(dissociateOccurrencesSignal(Akonadi::Item,QDate)), q, SIGNAL(dissociateOccurrencesSignal(Akonadi::Item,QDate)) ); q->connect( agenda, SIGNAL(newTodoSignal(QDate)), q, SIGNAL(newTodoSignal(QDate)) ); q->connect( agenda, SIGNAL(incidenceSelected(Akonadi::Item,QDate)), q, SLOT(slotSelectionChanged()) ); q->connect( agenda, SIGNAL(timeSpanSelectionChanged()), q, SLOT(slotClearTimeSpanSelection()) ); q->disconnect( agenda->agenda(), SIGNAL(zoomView(int,QPoint,Qt::Orientation)), agenda, 0 ); q->connect( agenda->agenda(), SIGNAL(zoomView(int,QPoint,Qt::Orientation)), q, SLOT(zoomView(int,QPoint,Qt::Orientation)) ); } AgendaView *lastView = mAgendaViews.last(); foreach ( AgendaView *agenda, mAgendaViews ) { if ( agenda != lastView ) { connect( agenda->agenda()->verticalScrollBar(), SIGNAL(valueChanged(int)), lastView->agenda()->verticalScrollBar(), SLOT(setValue(int)) ); } } foreach ( AgendaView *agenda, mAgendaViews ) { agenda->readSettings(); } int minWidth = 0; foreach ( QWidget *widget, mAgendaWidgets ) { minWidth = qMax( minWidth, widget->minimumSizeHint().width() ); } foreach ( QWidget *widget, mAgendaWidgets ) { widget->setMinimumWidth( minWidth ); } } MultiAgendaView::~MultiAgendaView() { delete d; } Akonadi::Item::List MultiAgendaView::selectedIncidences() const { Akonadi::Item::List list; foreach ( AgendaView *agendaView, d->mAgendaViews ) { list += agendaView->selectedIncidences(); } return list; } KCalCore::DateList MultiAgendaView::selectedIncidenceDates() const { KCalCore::DateList list; foreach ( AgendaView *agendaView, d->mAgendaViews ) { list += agendaView->selectedIncidenceDates(); } return list; } int MultiAgendaView::currentDateCount() const { foreach ( AgendaView *agendaView, d->mAgendaViews ) { return agendaView->currentDateCount(); } return 0; } void MultiAgendaView::showDates( const QDate &start, const QDate &end, const QDate &preferredMonth ) { Q_UNUSED( preferredMonth ); d->mStartDate = start; d->mEndDate = end; slotResizeScrollView(); d->mTimeLabelsZone->updateAll(); foreach ( AgendaView *agendaView, d->mAgendaViews ) { agendaView->showDates( start, end ); } } void MultiAgendaView::showIncidences( const Akonadi::Item::List &incidenceList, const QDate &date ) { foreach ( AgendaView *agendaView, d->mAgendaViews ) { agendaView->showIncidences( incidenceList, date ); } } void MultiAgendaView::updateView() { recreateViews(); foreach ( AgendaView *agendaView, d->mAgendaViews ) { agendaView->updateView(); } } int MultiAgendaView::maxDatesHint() const { // these maxDatesHint functions aren't used return AgendaView::MAX_DAY_COUNT; } void MultiAgendaView::slotSelectionChanged() { foreach ( AgendaView *agenda, d->mAgendaViews ) { if ( agenda != sender() ) { agenda->clearSelection(); } } } bool MultiAgendaView::eventDurationHint( QDateTime &startDt, QDateTime &endDt, bool &allDay ) const { foreach ( AgendaView *agenda, d->mAgendaViews ) { bool valid = agenda->eventDurationHint( startDt, endDt, allDay ); if ( valid ) { return true; } } return false; } void MultiAgendaView::slotClearTimeSpanSelection() { foreach ( AgendaView *agenda, d->mAgendaViews ) { if ( agenda != sender() ) { agenda->clearTimeSpanSelection(); } else { setCollectionId( agenda->collectionId() ); } } } AgendaView *MultiAgendaView::Private::createView( const QString &title ) { QWidget *box = new QWidget( mTopBox ); QVBoxLayout *layout = new QVBoxLayout( box ); layout->setMargin( 0 ); QLabel *l = new QLabel( title ); layout->addWidget( l ); l->setAlignment( Qt::AlignVCenter | Qt::AlignHCenter ); AgendaView *av = new AgendaView( q->preferences(), q->startDateTime().date(), q->endDateTime().date(), true, true, q ); layout->addWidget( av ); - av->setCalendar( q->calendar() ); + av->setCalendar( q->viewCalendar() ); av->setIncidenceChanger( q->changer() ); av->agenda()->scrollArea()->setVerticalScrollBarPolicy( Qt::ScrollBarAlwaysOff ); mAgendaViews.append( av ); mAgendaWidgets.append( box ); box->show(); mTimeLabelsZone->setAgendaView( av ); q->connect( mScrollBar, SIGNAL(valueChanged(int)), av->agenda()->verticalScrollBar(), SLOT(setValue(int)) ); q->connect( av->splitter(), SIGNAL(splitterMoved(int,int)), q, SLOT(resizeSplitters()) ); q->connect( av, SIGNAL(showIncidencePopupSignal(Akonadi::Item,QDate)), q, SIGNAL(showIncidencePopupSignal(Akonadi::Item,QDate)) ); q->connect( av, SIGNAL(showNewEventPopupSignal()), q, SIGNAL(showNewEventPopupSignal()) ); const QSize minHint = av->allDayAgenda()->scrollArea()->minimumSizeHint(); if ( minHint.isValid() ) { mLabel->setMinimumHeight( minHint.height() ); mRightDummyWidget->setMinimumHeight( minHint.height() ); } return av; } void MultiAgendaView::Private::addView( const Akonadi::Collection &collection ) { - AgendaView *av = createView( CalendarSupport::displayName( q->calendar().data(), collection ) ); + AgendaView *av = createView( CalendarSupport::displayName( q->etmCalendar().data(), collection ) ); av->setCollectionId( collection.id() ); } void MultiAgendaView::Private::addView( KCheckableProxyModel *sm, const QString &title ) { AgendaView *av = createView( title ); av->setCustomCollectionSelectionProxyModel( sm ); } void MultiAgendaView::resizeEvent( QResizeEvent *ev ) { d->resizeScrollView( ev->size() ); EventView::resizeEvent( ev ); } void MultiAgendaView::Private::resizeScrollView( const QSize &size ) { const int widgetWidth = size.width() - mTimeLabelsZone->width() - mScrollBar->width(); int height = size.height(); if ( mScrollArea->horizontalScrollBar()->isVisible() ) { // this should never happen, you can't get horizontalScrollBars const int sbHeight = mScrollArea->horizontalScrollBar()->height(); height -= sbHeight; mLeftBottomSpacer->setFixedHeight( sbHeight ); mRightBottomSpacer->setFixedHeight( sbHeight ); } else { mLeftBottomSpacer->setFixedHeight( 0 ); mRightBottomSpacer->setFixedHeight( 0 ); } mScrollArea->widget()->setFixedSize( widgetWidth, height ); mTopBox->resize( widgetWidth, height ); } void MultiAgendaView::setIncidenceChanger( Akonadi::IncidenceChanger *changer ) { EventView::setIncidenceChanger( changer ); foreach ( AgendaView *agenda, d->mAgendaViews ) { agenda->setIncidenceChanger( changer ); } } void MultiAgendaView::setPreferences( const PrefsPtr &prefs ) { foreach ( AgendaView *agenda, d->mAgendaViews ) { agenda->setPreferences( prefs ); } EventView::setPreferences( prefs ); } void MultiAgendaView::updateConfig() { EventView::updateConfig(); d->mTimeLabelsZone->setPreferences( preferences() ); d->mTimeLabelsZone->updateAll(); foreach ( AgendaView *agenda, d->mAgendaViews ) { agenda->updateConfig(); } } void MultiAgendaView::resizeSplitters() { if ( d->mAgendaViews.isEmpty() ) { return; } QSplitter *lastMovedSplitter = qobject_cast( sender() ); if ( !lastMovedSplitter ) { lastMovedSplitter = d->mLeftSplitter; } foreach ( AgendaView *agenda, d->mAgendaViews ) { if ( agenda->splitter() == lastMovedSplitter ) { continue; } agenda->splitter()->setSizes( lastMovedSplitter->sizes() ); } if ( lastMovedSplitter != d->mLeftSplitter ) { d->mLeftSplitter->setSizes( lastMovedSplitter->sizes() ); } if ( lastMovedSplitter != d->mRightSplitter ) { d->mRightSplitter->setSizes( lastMovedSplitter->sizes() ); } } void MultiAgendaView::zoomView( const int delta, const QPoint &pos, const Qt::Orientation ori ) { const int hourSz = preferences()->hourSize(); if ( ori == Qt::Vertical ) { if ( delta > 0 ) { if ( hourSz > 4 ) { preferences()->setHourSize( hourSz - 1 ); } } else { preferences()->setHourSize( hourSz + 1 ); } } foreach ( AgendaView *agenda, d->mAgendaViews ) { agenda->zoomView( delta, pos, ori ); } d->mTimeLabelsZone->updateAll(); } void MultiAgendaView::slotResizeScrollView() { d->resizeScrollView( size() ); } void MultiAgendaView::showEvent( QShowEvent *event ) { EventView::showEvent( event ); if ( d->mUpdateOnShow ) { d->mUpdateOnShow = false; d->mPendingChanges = true; // force a full view recreation showDates( d->mStartDate, d->mEndDate ); } } void MultiAgendaView::setChanges( Changes changes ) { EventView::setChanges( changes ); foreach ( AgendaView *agenda, d->mAgendaViews ) { agenda->setChanges( changes ); } } void MultiAgendaView::setupScrollBar() { if ( !d->mAgendaViews.isEmpty() && d->mAgendaViews.first()->agenda() ) { QScrollBar *scrollBar = d->mAgendaViews.first()->agenda()->verticalScrollBar(); d->mScrollBar->setMinimum( scrollBar->minimum() ); d->mScrollBar->setMaximum( scrollBar->maximum() ); d->mScrollBar->setSingleStep( scrollBar->singleStep() ); d->mScrollBar->setPageStep( scrollBar->pageStep() ); d->mScrollBar->setValue( scrollBar->value() ); } } void MultiAgendaView::collectionSelectionChanged() { kDebug(); d->mPendingChanges = true; recreateViews(); } bool MultiAgendaView::hasConfigurationDialog() const { /** The wrapper in korg has the dialog. Too complicated to move to CalendarViews. Depends on korg/AkonadiCollectionView, and will be refactored some day to get rid of CollectionSelectionProxyModel/EntityStateSaver */ return false; } void MultiAgendaView::doRestoreConfig( const KConfigGroup &configGroup ) { - if ( !calendar() ) { + if ( !calendars() ) { kError() << "Calendar is not set."; Q_ASSERT( false ); return; } d->mCustomColumnSetupUsed = configGroup.readEntry( "UseCustomColumnSetup", false ); d->mCustomNumberOfColumns = configGroup.readEntry( "CustomNumberOfColumns", 2 ); d->mCustomColumnTitles = configGroup.readEntry( "ColumnTitles", QStringList() ).toVector(); if ( d->mCustomColumnTitles.size() != d->mCustomNumberOfColumns ) { const int orig = d->mCustomColumnTitles.size(); d->mCustomColumnTitles.resize( d->mCustomNumberOfColumns ); for ( int i = orig; i < d->mCustomNumberOfColumns; ++i ) { d->mCustomColumnTitles[i] = generateColumnLabel( i ); } } QVector oldModels = d->mCollectionSelectionModels; d->mCollectionSelectionModels.clear(); if ( d->mCustomColumnSetupUsed ) { d->mCollectionSelectionModels.resize( d->mCustomNumberOfColumns ); for ( int i = 0; i < d->mCustomNumberOfColumns; ++i ) { // Sort the calanders by name QSortFilterProxyModel *sortProxy = new QSortFilterProxyModel( this ); sortProxy->setDynamicSortFilter( true ); - sortProxy->setSourceModel( calendar()->entityTreeModel() ); + sortProxy->setSourceModel( etmCalendar()->entityTreeModel() ); // Only show the first column KColumnFilterProxyModel *columnFilterProxy = new KColumnFilterProxyModel( this ); columnFilterProxy->setVisibleColumn( Akonadi::ETMCalendar::CollectionTitle ); columnFilterProxy->setSourceModel( sortProxy ); // Keep track of selection. QItemSelectionModel *qsm = new QItemSelectionModel( columnFilterProxy ); // Make the model checkable. KCheckableProxyModel *checkableProxy = new KCheckableProxyModel( this ); checkableProxy->setSourceModel( columnFilterProxy ); checkableProxy->setSelectionModel( qsm ); const QString groupName = configGroup.name() + QLatin1String("_subView_") + QString::number( i ); const KConfigGroup group = configGroup.config()->group( groupName ); if ( !d->mSelectionSavers.contains( groupName ) ) { d->mSelectionSavers.insert( groupName, new KViewStateMaintainer( group ) ); d->mSelectionSavers[groupName]->setSelectionModel( checkableProxy->selectionModel() ); } d->mSelectionSavers[groupName]->restoreState(); d->mCollectionSelectionModels[i] = checkableProxy; } } d->mPendingChanges = true; recreateViews(); qDeleteAll( oldModels ); } void MultiAgendaView::doSaveConfig( KConfigGroup &configGroup ) { configGroup.writeEntry( "UseCustomColumnSetup", d->mCustomColumnSetupUsed ); configGroup.writeEntry( "CustomNumberOfColumns", d->mCustomNumberOfColumns ); const QStringList titleList = d->mCustomColumnTitles.toList(); configGroup.writeEntry( "ColumnTitles", titleList ); int idx = 0; foreach ( KCheckableProxyModel *checkableProxyModel, d->mCollectionSelectionModels ) { const QString groupName = configGroup.name() + QLatin1String("_subView_") + QString::number( idx ); KConfigGroup group = configGroup.config()->group( groupName ); ++idx; //TODO never used ? KViewStateMaintainer saver( group ); if ( !d->mSelectionSavers.contains( groupName ) ) { d->mSelectionSavers.insert( groupName, new KViewStateMaintainer( group ) ); d->mSelectionSavers[groupName]->setSelectionModel( checkableProxyModel->selectionModel() ); } d->mSelectionSavers[groupName]->saveState(); } } void MultiAgendaView::customCollectionsChanged( ConfigDialogInterface *dlg ) { if ( !d->mCustomColumnSetupUsed && !dlg->useCustomColumns() ) { // Config didn't change, no need to recreate views return; } d->mCustomColumnSetupUsed = dlg->useCustomColumns(); d->mCustomNumberOfColumns = dlg->numberOfColumns(); QVector newModels; newModels.resize( d->mCustomNumberOfColumns ); d->mCustomColumnTitles.resize( d->mCustomNumberOfColumns ); for ( int i = 0; i < d->mCustomNumberOfColumns; ++i ) { newModels[i] = dlg->takeSelectionModel( i ); d->mCustomColumnTitles[i] = dlg->columnTitle( i ); } d->mCollectionSelectionModels = newModels; d->mPendingChanges = true; recreateViews(); } bool MultiAgendaView::customColumnSetupUsed() const { return d->mCustomColumnSetupUsed; } int MultiAgendaView::customNumberOfColumns() const { return d->mCustomNumberOfColumns; } QVector MultiAgendaView::collectionSelectionModels() const { return d->mCollectionSelectionModels; } QVector MultiAgendaView::customColumnTitles() const { return d->mCustomColumnTitles; } diff --git a/calendarviews/todo/todomodel.cpp b/calendarviews/todo/todomodel.cpp index 6a233ba658..d73ef51176 100644 --- a/calendarviews/todo/todomodel.cpp +++ b/calendarviews/todo/todomodel.cpp @@ -1,923 +1,904 @@ /* Copyright (c) 2008 Thomas Thrainer Copyright (c) 2012 Sérgio Martins This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #include "todomodel_p.h" #include "incidencetreemodel.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include struct SourceModelIndex { SourceModelIndex( int _r, int _c, void *_p, QAbstractItemModel *_m ) : r(_r), c(_c), p(_p), m(_m) { } operator QModelIndex() { return reinterpret_cast( *this ); } int r, c; void *p; const QAbstractItemModel *m; }; static bool isDueToday( const KCalCore::Todo::Ptr &todo ) { return !todo->isCompleted() && todo->dtDue().date() == QDate::currentDate(); } TodoModel::Private::Private( const EventViews::PrefsPtr &preferences, TodoModel *qq ) : QObject(), m_changer(0) , m_preferences( preferences ) , q( qq ) { } -Akonadi::Item TodoModel::Private::findItemByUid( const QString &uid, - const QModelIndex &parent ) const -{ - Q_ASSERT( !uid.isEmpty() ); - IncidenceTreeModel *treeModel = qobject_cast( q->sourceModel() ); - if ( treeModel ) { // O(1) Shortcut - return treeModel->item( uid ); - } - - Akonadi::Item item; - const int count = q->rowCount( parent ); - for ( int i=0; iindex( i, 0, parent ); - Q_ASSERT( currentIndex.isValid() ); - item = q->data( currentIndex, Akonadi::EntityTreeModel::ItemRole ).value(); - if ( item.isValid() ) { - return item; - } else { - item = findItemByUid( uid, currentIndex ); - if ( item.isValid() ) { - return item; - } - } - } - - return item; -} - void TodoModel::Private::onDataChanged( const QModelIndex &begin, const QModelIndex &end ) { Q_ASSERT( begin.isValid() ); Q_ASSERT( end.isValid() ); const QModelIndex proxyBegin = q->mapFromSource( begin ); Q_ASSERT( proxyBegin.column() == 0 ); const QModelIndex proxyEnd = q->mapFromSource( end ); emit q->dataChanged( proxyBegin, proxyEnd.sibling( proxyEnd.row(), TodoModel::ColumnCount-1 ) ); } void TodoModel::Private::onHeaderDataChanged( Qt::Orientation orientation, int first, int last ) { emit q->headerDataChanged( orientation, first, last ); } void TodoModel::Private::onRowsAboutToBeInserted( const QModelIndex &parent, int begin, int end ) { const QModelIndex index = q->mapFromSource( parent ); Q_ASSERT( !( parent.isValid() ^ index.isValid() ) ); // Both must be valid, or both invalid Q_ASSERT( !( index.isValid() && index.model() != q ) ); q->beginInsertRows( index, begin, end ); } void TodoModel::Private::onRowsInserted( const QModelIndex &, int, int ) { q->endInsertRows(); } void TodoModel::Private::onRowsAboutToBeRemoved( const QModelIndex &parent, int begin, int end ) { const QModelIndex index = q->mapFromSource( parent ); Q_ASSERT( !( parent.isValid() ^ index.isValid() ) ); // Both must be valid, or both invalid Q_ASSERT( !( index.isValid() && index.model() != q ) ); q->beginRemoveRows( index, begin, end ); } void TodoModel::Private::onRowsRemoved( const QModelIndex &, int, int ) { q->endRemoveRows(); } void TodoModel::Private::onRowsAboutToBeMoved( const QModelIndex &sourceParent, int sourceStart, int sourceEnd, const QModelIndex &destinationParent, int destinationRow ) { Q_UNUSED( sourceParent ); Q_UNUSED( sourceStart ); Q_UNUSED( sourceEnd ); Q_UNUSED( destinationParent ); Q_UNUSED( destinationRow ); /* Disabled for now, layoutAboutToBeChanged() is emitted q->beginMoveRows( q->mapFromSource( sourceParent ), sourceStart, sourceEnd, q->mapFromSource( destinationParent ), destinationRow ); */ } void TodoModel::Private::onRowsMoved( const QModelIndex &, int, int, const QModelIndex &, int ) { /*q->endMoveRows();*/ } void TodoModel::Private::onModelAboutToBeReset() { q->beginResetModel(); } void TodoModel::Private::onModelReset() { q->endResetModel(); } void TodoModel::Private::onLayoutAboutToBeChanged() { Q_ASSERT( m_persistentIndexes.isEmpty() ); Q_ASSERT( m_layoutChangePersistentIndexes.isEmpty() ); Q_ASSERT( m_columns.isEmpty() ); QModelIndexList persistentIndexes = q->persistentIndexList(); foreach ( const QPersistentModelIndex &persistentIndex, persistentIndexes ) { m_persistentIndexes << persistentIndex; // Stuff we have to update onLayoutChanged Q_ASSERT( persistentIndex.isValid() ); QModelIndex index_col0 = q->createIndex( persistentIndex.row(), 0, persistentIndex.internalPointer() ); const QPersistentModelIndex srcPersistentIndex = q->mapToSource( index_col0 ); Q_ASSERT( srcPersistentIndex.isValid() ); m_layoutChangePersistentIndexes << srcPersistentIndex; m_columns << persistentIndex.column(); } emit q->layoutAboutToBeChanged(); } void TodoModel::Private::onLayoutChanged() { for ( int i = 0; i < m_persistentIndexes.size(); ++i ) { QModelIndex newIndex_col0 = q->mapFromSource( m_layoutChangePersistentIndexes.at( i ) ); Q_ASSERT( newIndex_col0.isValid() ); const int column = m_columns.at( i ); QModelIndex newIndex = column == 0 ? newIndex_col0 : q->createIndex( newIndex_col0.row(), column, newIndex_col0.internalPointer() ); q->changePersistentIndex( m_persistentIndexes.at( i ), newIndex ); } m_layoutChangePersistentIndexes.clear(); m_persistentIndexes.clear(); m_columns.clear(); emit q->layoutChanged(); } TodoModel::TodoModel( const EventViews::PrefsPtr &preferences, QObject *parent ) : QAbstractProxyModel( parent ), d( new Private( preferences, this ) ) { setObjectName( QLatin1String("TodoModel") ); } TodoModel::~TodoModel() { delete d; } QVariant TodoModel::data( const QModelIndex &index, int role ) const { Q_ASSERT( index.isValid() ); if ( !index.isValid() || !d->m_calendar ) { return QVariant(); } const QModelIndex sourceIndex = mapToSource( index.sibling( index.row(), 0 ) ); if ( !sourceIndex.isValid() ) { return QVariant(); } Q_ASSERT( sourceIndex.isValid() ); const Akonadi::Item item = sourceIndex.data( Akonadi::EntityTreeModel::ItemRole ).value(); if ( !item.isValid() ) { kWarning() << "Invalid index: " << sourceIndex; //Q_ASSERT( false ); return QVariant(); } const KCalCore::Todo::Ptr todo = CalendarSupport::todo( item ); if ( !todo ) { kError() << "item.hasPayload()" << item.hasPayload(); if ( item.hasPayload() ) { KCalCore::Incidence::Ptr incidence = item.payload(); if ( incidence ) kError() << "It's actually " << incidence->type(); } Q_ASSERT(!"There's no to-do."); return QVariant(); } if ( role == Qt::DisplayRole ) { switch ( index.column() ) { case SummaryColumn: return QVariant( todo->summary() ); case RecurColumn: if (todo->recurs()) { if (todo->hasRecurrenceId()) { return i18nc( "yes, an exception to a recurring to-do", "Exception" ); } else { return i18nc( "yes, recurring to-do", "Yes" ); } } else { return i18nc( "no, not a recurring to-do", "No" ); } case PriorityColumn: if ( todo->priority() == 0 ) { return QVariant( QString::fromLatin1( "--" ) ); } return QVariant( todo->priority() ); case PercentColumn: return QVariant( todo->percentComplete() ); case StartDateColumn: return todo->hasStartDate() ? QVariant( KCalUtils::IncidenceFormatter::dateToString( todo->dtStart() ) ) : QVariant( QString() ); case DueDateColumn: return todo->hasDueDate() ? QVariant( KCalUtils::IncidenceFormatter::dateToString( todo->dtDue() ) ) : QVariant( QString() ); case CategoriesColumn: { QString categories = todo->categories().join( i18nc( "delimiter for joining category names", "," ) ); return QVariant( categories ); } case DescriptionColumn: return QVariant( todo->description() ); case CalendarColumn: return QVariant( CalendarSupport::displayName( d->m_calendar.data(), item.parentCollection() ) ); } return QVariant(); } if ( role == Qt::EditRole ) { switch ( index.column() ) { case SummaryColumn: return QVariant( todo->summary() ); case RecurColumn: return QVariant( todo->recurs() ); case PriorityColumn: return QVariant( todo->priority() ); case PercentColumn: return QVariant( todo->percentComplete() ); case StartDateColumn: return QVariant( todo->dtStart().date() ); case DueDateColumn: return QVariant( todo->dtDue().date() ); case CategoriesColumn: return QVariant( todo->categories() ); case DescriptionColumn: return QVariant( todo->description() ); case CalendarColumn: return QVariant( CalendarSupport::displayName( d->m_calendar.data(), item.parentCollection() ) ); } return QVariant(); } // set the tooltip for every item if ( role == Qt::ToolTipRole ) { if ( d->m_preferences->enableToolTips() ) { return QVariant( KCalUtils::IncidenceFormatter::toolTipStr( CalendarSupport::displayName( d->m_calendar.data(), item.parentCollection() ), todo, QDate(), true, CalendarSupport::KCalPrefs::instance()->timeSpec() ) ); } else { return QVariant(); } } // background colour for todos due today or overdue todos if ( role == Qt::BackgroundRole ) { if ( todo->isOverdue() ) { return QVariant( QBrush( d->m_preferences->todoOverdueColor() ) ); } else if ( isDueToday( todo ) ) { return QVariant( QBrush( d->m_preferences->todoDueTodayColor() ) ); } } // indicate if a row is checked (=completed) only in the first column if ( role == Qt::CheckStateRole && index.column() == 0 ) { if (hasChildren(index) && !index.parent().isValid()) { return QVariant(); } if ( todo->isCompleted() ) { return QVariant( Qt::Checked ); } else { return QVariant( Qt::Unchecked ); } } // icon for recurring todos // It's in the summary column so you don't accidentally click // the checkbox ( which increments the next occurrence date ). if ( role == Qt::DecorationRole && index.column() == SummaryColumn ) { if ( todo->recurs() ) { return QVariant( QIcon( SmallIcon( QLatin1String("task-recurring") ) ) ); } } // category colour if ( role == Qt::DecorationRole && index.column() == SummaryColumn ) { QStringList categories = todo->categories(); return categories.isEmpty() ? QVariant() : QVariant( CalendarSupport::KCalPrefs::instance()->categoryColor( categories.first() ) ); } else if ( role == Qt::DecorationRole ) { return QVariant(); } if ( role == TodoRole ) { QVariant ret( QMetaType::VoidStar ); ret.setValue( item ); return ret; } if ( role == IsRichTextRole ) { if ( index.column() == SummaryColumn ) { return QVariant( todo->summaryIsRich() ); } else if ( index.column() == DescriptionColumn ) { return QVariant( todo->descriptionIsRich() ); } else { return QVariant(); } } if ( role == Qt::TextAlignmentRole ) { switch ( index.column() ) { // If you change this, change headerData() too. case RecurColumn: case PriorityColumn: case PercentColumn: case StartDateColumn: case DueDateColumn: case CategoriesColumn: case CalendarColumn: return QVariant( Qt::AlignHCenter | Qt::AlignVCenter ); } return QVariant( Qt::AlignLeft | Qt::AlignVCenter ); } if ( sourceModel() ) { return sourceModel()->data( mapToSource( index.sibling( index.row(), 0 ) ), role ); } return QVariant(); } bool TodoModel::setData( const QModelIndex &index, const QVariant &value, int role ) { Q_ASSERT( index.isValid() ); if ( !d->m_changer ) { return false; } const QVariant oldValue = data( index, role ); if ( oldValue == value ) { // Nothing changed, the user used one of the QStyledDelegate's editors but seted the old value // Lets just skip this then and avoid a roundtrip to akonadi, and avoid sending invitations return true; } const Akonadi::Item item = data( index, Akonadi::EntityTreeModel::ItemRole ).value(); const KCalCore::Todo::Ptr todo = CalendarSupport::todo( item ); if ( !item.isValid() || !todo ) { kWarning() << "TodoModel::setData() called, bug item is invalid or doesn't have payload"; Q_ASSERT( false ); return false; } if ( d->m_calendar->hasRight( item, Akonadi::Collection::CanChangeItem ) ) { KCalCore::Todo::Ptr oldTodo( todo->clone() ); if ( role == Qt::CheckStateRole && index.column() == 0 ) { const bool checked = static_cast( value.toInt() ) == Qt::Checked; if ( checked ) todo->setCompleted( KDateTime::currentLocalDateTime() ); // Because it calls Todo::recurTodo() else todo->setCompleted( false ); } if ( role == Qt::EditRole ) { switch ( index.column() ) { case SummaryColumn: if ( !value.toString().isEmpty() ) { todo->setSummary( value.toString() ); } break; case PriorityColumn: todo->setPriority( value.toInt() ); break; case PercentColumn: todo->setPercentComplete( value.toInt() ); break; case StartDateColumn: { KDateTime tmp = todo->dtStart(); tmp.setDate( value.toDate() ); todo->setDtStart( tmp ); } break; case DueDateColumn: { KDateTime tmp = todo->dtDue(); tmp.setDate( value.toDate() ); todo->setDtDue( tmp ); } break; case CategoriesColumn: todo->setCategories( value.toStringList() ); break; case DescriptionColumn: todo->setDescription( value.toString() ); break; } } if ( !todo->dirtyFields().isEmpty() ) { d->m_changer->modifyIncidence( item, oldTodo ); // modifyIncidence will eventually call the view's // changeIncidenceDisplay method, which in turn // will call processChange. processChange will then emit // dataChanged to the view, so we don't have to // do it here } return true; } else { if ( !( role == Qt::CheckStateRole && index.column() == 0 ) ) { //KOHelper::showSaveIncidenceErrorMsg( 0, todo ); //TODO pass parent kError() << "Unable to modify incidence"; } return false; } } int TodoModel::rowCount( const QModelIndex &parent ) const { if ( sourceModel() ) { if ( parent.isValid() ) { QModelIndex parent_col0 = createIndex( parent.row(), 0, parent.internalPointer() ); return sourceModel()->rowCount( mapToSource( parent_col0 ) ); } else { return sourceModel()->rowCount(); } } return 0; } int TodoModel::columnCount( const QModelIndex & ) const { return ColumnCount; } void TodoModel::setSourceModel( QAbstractItemModel *model ) { if ( model == sourceModel() ) { return; } beginResetModel(); if ( sourceModel() ) { disconnect( sourceModel(), SIGNAL(dataChanged(QModelIndex,QModelIndex)), d, SLOT(onDataChanged(QModelIndex,QModelIndex)) ); disconnect( sourceModel(), SIGNAL(headerDataChanged(Qt::Orientation,int,int)), d, SLOT(onHeaderDataChanged(Qt::Orientation,int,int)) ); disconnect( sourceModel(), SIGNAL(rowsInserted(QModelIndex,int,int)), d, SLOT(onRowsInserted(QModelIndex,int,int)) ); disconnect( sourceModel(), SIGNAL(rowsRemoved(QModelIndex,int,int)), d, SLOT(onRowsRemoved(QModelIndex,int,int)) ); disconnect( sourceModel(), SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)), d, SLOT(onRowsMoved(QModelIndex,int,int,QModelIndex,int)) ); disconnect( sourceModel(), SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)), d, SLOT(onRowsAboutToBeInserted(QModelIndex,int,int)) ); disconnect( sourceModel(), SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), d, SLOT(onRowsAboutToBeRemoved(QModelIndex,int,int)) ); disconnect( sourceModel(), SIGNAL(modelAboutToBeReset()), d, SLOT(onModelAboutToBeReset()) ); disconnect( sourceModel(), SIGNAL(modelReset()), d, SLOT(onModelReset()) ); disconnect( sourceModel(), SIGNAL(layoutAboutToBeChanged()), d, SLOT(onLayoutAboutToBeChanged()) ); disconnect( sourceModel(), SIGNAL(layoutChanged()), d, SLOT(onLayoutChanged()) ); } QAbstractProxyModel::setSourceModel( model ); if ( sourceModel() ) { connect( sourceModel(), SIGNAL(dataChanged(QModelIndex,QModelIndex)), d, SLOT(onDataChanged(QModelIndex,QModelIndex)) ); connect( sourceModel(), SIGNAL(headerDataChanged(Qt::Orientation,int,int)), d, SLOT(onHeaderDataChanged(Qt::Orientation,int,int)) ); connect( sourceModel(), SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)), d, SLOT(onRowsAboutToBeInserted(QModelIndex,int,int)) ); connect( sourceModel(), SIGNAL(rowsInserted(QModelIndex,int,int)), d, SLOT(onRowsInserted(QModelIndex,int,int)) ); connect( sourceModel(), SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), d, SLOT(onRowsAboutToBeRemoved(QModelIndex,int,int)) ); connect( sourceModel(), SIGNAL(rowsRemoved(QModelIndex,int,int)), d, SLOT(onRowsRemoved(QModelIndex,int,int)) ); connect( sourceModel(), SIGNAL(rowsAboutToBeMoved(QModelIndex,int,int,QModelIndex,int)), d, SLOT(onRowsAboutToBeMoved(QModelIndex,int,int,QModelIndex,int)) ); connect( sourceModel(), SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)), d, SLOT(onRowsMoved(QModelIndex,int,int,QModelIndex,int)) ); connect( sourceModel(), SIGNAL(modelAboutToBeReset()), d, SLOT(onModelAboutToBeReset()) ); connect( sourceModel(), SIGNAL(modelReset()), d, SLOT(onModelReset()) ); connect( sourceModel(), SIGNAL(layoutAboutToBeChanged()), d, SLOT(onLayoutAboutToBeChanged()) ); connect( sourceModel(), SIGNAL(layoutChanged()), d, SLOT(onLayoutChanged()) ); } endResetModel(); } void TodoModel::setIncidenceChanger( Akonadi::IncidenceChanger *changer ) { d->m_changer = changer; } QVariant TodoModel::headerData( int column, Qt::Orientation orientation, int role ) const { if ( orientation != Qt::Horizontal ) { return QVariant(); } if ( role == Qt::DisplayRole ) { switch ( column ) { case SummaryColumn: return QVariant( i18n( "Summary" ) ); case RecurColumn: return QVariant( i18n( "Recurs" ) ); case PriorityColumn: return QVariant( i18n( "Priority" ) ); case PercentColumn: return QVariant( i18nc( "@title:column percent complete", "Complete" ) ); case StartDateColumn: return QVariant( i18n( "Start Date" ) ); case DueDateColumn: return QVariant( i18n( "Due Date" ) ); case CategoriesColumn: return QVariant( i18n( "Categories" ) ); case DescriptionColumn: return QVariant( i18n( "Description" ) ); case CalendarColumn: return QVariant( i18n( "Calendar" ) ); } } if ( role == Qt::TextAlignmentRole ) { switch ( column ) { // If you change this, change data() too. case RecurColumn: case PriorityColumn: case PercentColumn: case StartDateColumn: case DueDateColumn: case CategoriesColumn: case CalendarColumn: return QVariant( Qt::AlignHCenter ); } return QVariant(); } return QVariant(); } void TodoModel::setCalendar( const Akonadi::ETMCalendar::Ptr &calendar ) { d->m_calendar = calendar; } Qt::DropActions TodoModel::supportedDropActions() const { // Qt::CopyAction not supported yet return Qt::MoveAction; } QStringList TodoModel::mimeTypes() const { static QStringList list; if ( list.isEmpty() ) { list << KCalUtils::ICalDrag::mimeType() << KCalUtils::VCalDrag::mimeType(); } return list; } QMimeData *TodoModel::mimeData( const QModelIndexList &indexes ) const { Akonadi::Item::List items; Q_FOREACH ( const QModelIndex &index, indexes ) { const Akonadi::Item item = this->data( index, Akonadi::EntityTreeModel::ItemRole ).value(); if ( item.isValid() && !items.contains( item ) ) { items.push_back( item ); } } return CalendarSupport::createMimeData( items, d->m_calendar->timeSpec() ); } bool TodoModel::dropMimeData( const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent ) { Q_UNUSED( row ); Q_UNUSED( column ); if ( action != Qt::MoveAction ) { kWarning() << "No action other than MoveAction currently supported!"; //TODO return false; } if ( d->m_calendar && d->m_changer && ( KCalUtils::ICalDrag::canDecode( data ) || KCalUtils::VCalDrag::canDecode( data ) ) ) { KCalUtils::DndFactory dndFactory( d->m_calendar ); KCalCore::Todo::Ptr t = dndFactory.createDropTodo( data ); KCalCore::Event::Ptr e = dndFactory.createDropEvent( data ); if ( t ) { + KCalCore::Todo::Ptr destTodo; + KCalCore::Todo::Ptr todo; + // we don't want to change the created todo, but the one which is already // stored in our calendar / tree - const Akonadi::Item item = d->findItemByUid( t->uid(), QModelIndex() ); - KCalCore::Todo::Ptr todo = CalendarSupport::todo( item ); - KCalCore::Todo::Ptr destTodo; + QStringList calendars = d->m_calendar->calendars(t->uid(), t->recurrenceId()); + if (!calendars.isEmpty()) { + //FIXME don't just take the first calendar + todo = d->m_calendar->incidence(calendars.first(), t->uid(), t->recurrenceId()).dynamicCast(); + } if ( parent.isValid() ) { const Akonadi::Item parentItem = - this->data( parent, Akonadi::EntityTreeModel::ItemRole ).value(); + this->data( parent, Akonadi::EntityTreeModel::ItemRole ).value(); if ( parentItem.isValid() ) { destTodo = CalendarSupport::todo( parentItem ); } } KCalCore::Incidence::Ptr tmp = destTodo; while ( tmp ) { if ( tmp->uid() == todo->uid() ) { //correct, don't use instanceIdentifier() here KMessageBox::information( 0, i18n( "Cannot move to-do to itself or a child of itself." ), i18n( "Drop To-do" ), QLatin1String("NoDropTodoOntoItself") ); return false; } const QString parentUid = tmp->relatedTo(); - tmp = CalendarSupport::incidence( d->m_calendar->item( parentUid ) ); + QStringList calendars = d->m_calendar->calendars(tmp->relatedTo()); + if (!calendars.isEmpty()) { + //FIXME don't just take the first calendar + tmp = d->m_calendar->incidence(calendars.first(), tmp->relatedTo()); + } } if (!destTodo || !destTodo->hasRecurrenceId()) { KCalCore::Todo::Ptr oldTodo = KCalCore::Todo::Ptr( todo->clone() ); // destTodo is empty when we drag a to-do out of a relationship todo->setRelatedTo( destTodo ? destTodo->uid() : QString() ); - d->m_changer->modifyIncidence( item, oldTodo ); + d->m_changer->modifyIncidence( d->m_calendar->item(todo), oldTodo ); // again, no need to emit dataChanged, that's done by processChange return true; } else { kDebug() << "Todo's with recurring id can't have child todos yet."; return false; } } else if ( e ) { // TODO: Implement dropping an event onto a to-do: Generate a relationship to the event! } else { if ( !parent.isValid() ) { // TODO we should create a new todo with the data in the drop object kDebug() << "TODO: Create a new todo with the given data"; return false; } const Akonadi::Item parentItem = this->data( parent, Akonadi::EntityTreeModel::ItemRole ).value(); KCalCore::Todo::Ptr destTodo = CalendarSupport::todo( parentItem ); if ( data->hasText() ) { QString text = data->text(); KCalCore::Todo::Ptr oldTodo = KCalCore::Todo::Ptr( destTodo->clone() ); if ( text.startsWith( QLatin1String( "file:" ) ) ) { destTodo->addAttachment( KCalCore::Attachment::Ptr( new KCalCore::Attachment( text ) ) ); } else { QStringList emails = KPIMUtils::splitAddressList( text ); for ( QStringList::ConstIterator it = emails.constBegin(); it != emails.constEnd(); ++it ) { QString name, email, comment; if ( KPIMUtils::splitAddress( *it, name, email, comment ) == KPIMUtils::AddressOk ) { destTodo->addAttendee( KCalCore::Attendee::Ptr( new KCalCore::Attendee( name, email ) ) ); } } } d->m_changer->modifyIncidence( parentItem, oldTodo ); return true; } } } return false; } Qt::ItemFlags TodoModel::flags( const QModelIndex &index ) const { if ( !index.isValid() ) { return 0; } Qt::ItemFlags ret = QAbstractItemModel::flags( index ); const Akonadi::Item item = data( index, Akonadi::EntityTreeModel::ItemRole ).value(); if ( !item.isValid() ) { Q_ASSERT( mapToSource( index ).isValid() ); kWarning() << "Item is invalid " << index; Q_ASSERT( false ); return 0; } ret |= Qt::ItemIsDragEnabled; const KCalCore::Todo::Ptr todo = CalendarSupport::todo( item ); if ( d->m_calendar->hasRight( item, Akonadi::Collection::CanChangeItem ) ) { // the following columns are editable: switch ( index.column() ) { case SummaryColumn: case PriorityColumn: case PercentColumn: case StartDateColumn: case DueDateColumn: case CategoriesColumn: ret |= Qt::ItemIsEditable; break; case DescriptionColumn: if ( !todo->descriptionIsRich() ) { ret |= Qt::ItemIsEditable; } break; } } if ( index.column() == 0 ) { // whole rows should have checkboxes, so append the flag for the // first item of every row only. Also, only the first item of every // row should be used as a target for a drag and drop operation. ret |= Qt::ItemIsUserCheckable | Qt::ItemIsDropEnabled; } return ret; } QModelIndex TodoModel::mapFromSource( const QModelIndex &sourceIndex ) const { if ( !sourceModel() || !sourceIndex.isValid() ) { return QModelIndex(); } Q_ASSERT( sourceIndex.internalPointer() ); return createIndex( sourceIndex.row(), 0, sourceIndex.internalPointer() ); } QModelIndex TodoModel::mapToSource( const QModelIndex &proxyIndex ) const { if ( !sourceModel() || !proxyIndex.isValid() ) { return QModelIndex(); } if ( proxyIndex.column() != 0 ) { kError() << "Map to source called with column>0, but source model only has 1 column"; Q_ASSERT( false ); } Q_ASSERT( proxyIndex.internalPointer() ); // we convert to column 0 const QModelIndex sourceIndex = SourceModelIndex( proxyIndex.row(), 0, proxyIndex.internalPointer(), sourceModel() ); return sourceIndex; } QModelIndex TodoModel::index( int row, int column, const QModelIndex &parent ) const { if ( !sourceModel() ) { return QModelIndex(); } Q_ASSERT( !parent.isValid() || parent.internalPointer() ); QModelIndex parent_col0 = parent.isValid() ? createIndex( parent.row(), 0, parent.internalPointer() ) : QModelIndex(); // Lets preserve the original internalPointer const QModelIndex index = mapFromSource( sourceModel()->index( row, 0, mapToSource( parent_col0 ) ) ); Q_ASSERT( !index.isValid() || index.internalPointer() ); if ( index.isValid() ) { return createIndex( row, column, index.internalPointer() ); } return QModelIndex(); } QModelIndex TodoModel::parent( const QModelIndex &child ) const { if ( !sourceModel() || !child.isValid() ) { return QModelIndex(); } Q_ASSERT( child.internalPointer() ); const QModelIndex child_col0 = createIndex( child.row(), 0, child.internalPointer() ); QModelIndex parentIndex = mapFromSource( sourceModel()->parent( mapToSource( child_col0 ) ) ); Q_ASSERT( !parentIndex.isValid() || parentIndex.internalPointer() ); if ( parentIndex.isValid() ) { // preserve original column return createIndex( parentIndex.row(), child.column(), parentIndex.internalPointer() ); } return QModelIndex(); } QModelIndex TodoModel::buddy( const QModelIndex &index ) const { // We reimplement because the default implementation calls mapToSource() and // source model doesn't have the same number of columns. return index; } diff --git a/calendarviews/todo/todoview.cpp b/calendarviews/todo/todoview.cpp index 6595ba4c01..242345f0b9 100644 --- a/calendarviews/todo/todoview.cpp +++ b/calendarviews/todo/todoview.cpp @@ -1,1288 +1,1288 @@ /* This file is part of KOrganizer. Copyright (c) 2000,2001,2003 Cornelius Schumacher Copyright (c) 2003-2004 Reinhold Kainhofer Copyright (c) 2005 Rafal Rzepecki Copyright (c) 2008 Thomas Thrainer Copyright (c) 2013 Sérgio Martins This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #include "todoview.h" #include "incidencetreemodel.h" #include "tododelegates.h" #include "todomodel.h" #include "todoviewsortfilterproxymodel.h" #include "todoviewquickaddline.h" #include "todoviewquicksearch.h" #include "todoviewview.h" #include "helper.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include Q_DECLARE_METATYPE(QPointer) using namespace EventViews; using namespace KCalCore; namespace EventViews { // We share this struct between all views, for performance and memory purposes class ModelStack { public: ModelStack( const EventViews::PrefsPtr &preferences, QObject *parent_ ) : todoModel( new TodoModel( preferences ) ), parent( parent_ ), calendar( 0 ), todoTreeModel( 0 ), todoFlatModel( 0 ), prefs( preferences ) { } ~ModelStack() { delete todoModel; delete todoTreeModel; delete todoFlatModel; } void registerView( TodoView *view ) { views << view; } void unregisterView( TodoView *view ) { views.removeAll( view ); } void setFlatView( bool flat ) { const QString todoMimeType = QLatin1String( "application/x-vnd.akonadi.calendar.todo" ); if ( flat ) { foreach ( TodoView *view, views ) { // In flatview dropping confuses users and it's very easy to drop into a child item view->mView->setDragDropMode( QAbstractItemView::DragOnly ); view->setFlatView( flat, /**propagate=*/false ); // So other views update their toggle icon if ( todoTreeModel ) { view->saveViewState(); // Save the tree state before it's gone } } delete todoFlatModel; todoFlatModel = new Akonadi::EntityMimeTypeFilterModel( parent ); todoFlatModel->addMimeTypeInclusionFilter( todoMimeType ); todoFlatModel->setSourceModel( calendar ? calendar->model() : 0 ); todoModel->setSourceModel( todoFlatModel ); delete todoTreeModel; todoTreeModel = 0; } else { delete todoTreeModel; todoTreeModel = new IncidenceTreeModel( QStringList() << todoMimeType, parent ); foreach ( TodoView *view, views ) { QObject::connect( todoTreeModel, SIGNAL(indexChangedParent(QModelIndex)), view, SLOT(expandIndex(QModelIndex)) ); QObject::connect( todoTreeModel, SIGNAL(batchInsertionFinished()), view, SLOT(restoreViewState()) ); view->mView->setDragDropMode( QAbstractItemView::DragDrop ); view->setFlatView( flat, /**propagate=*/false ); // So other views update their toggle icon } todoTreeModel->setSourceModel( calendar ? calendar->model() : 0 ); todoModel->setSourceModel( todoTreeModel ); delete todoFlatModel; todoFlatModel = 0; } foreach ( TodoView *view, views ) { view->mFlatViewButton->blockSignals( true ); // We block signals to avoid recursion, we have two TodoViews and mFlatViewButton is synchronized view->mFlatViewButton->setChecked( flat ); view->mFlatViewButton->blockSignals( false ); view->mView->setRootIsDecorated( !flat ); view->restoreViewState(); } prefs->setFlatListTodo( flat ); prefs->writeConfig(); } void setCalendar( const Akonadi::ETMCalendar::Ptr &newCalendar ) { calendar = newCalendar; todoModel->setCalendar( calendar ); if ( todoTreeModel ) { todoTreeModel->setSourceModel( calendar ? calendar->model() : 0 ); } } bool isFlatView() const { return todoFlatModel != 0; } TodoModel *todoModel; QList views; QObject *parent; Akonadi::ETMCalendar::Ptr calendar; IncidenceTreeModel *todoTreeModel; Akonadi::EntityMimeTypeFilterModel *todoFlatModel; EventViews::PrefsPtr prefs; }; } // Don't use K_GLOBAL_STATIC, see QTBUG-22667 static ModelStack *sModels = 0; TodoView::TodoView( const EventViews::PrefsPtr &prefs, bool sidebarView, QWidget *parent ) : EventView( parent ) , mQuickSearch( 0 ) , mQuickAdd( 0 ) , mTreeStateRestorer( 0 ) , mSidebarView( sidebarView ) , mResizeColumnsScheduled( false ) { mResizeColumnsTimer = new QTimer( this ); connect( mResizeColumnsTimer, SIGNAL(timeout()), SLOT(resizeColumns()) ); mResizeColumnsTimer->setInterval( 100 ); // so we don't overdue it when user resizes window manually mResizeColumnsTimer->setSingleShot( true ); setPreferences( prefs ); if ( !sModels ) { sModels = new ModelStack( prefs, parent ); } sModels->registerView( this ); mProxyModel = new TodoViewSortFilterProxyModel( preferences(), this ); mProxyModel->setSourceModel( sModels->todoModel ); mProxyModel->setDynamicSortFilter( true ); mProxyModel->setFilterKeyColumn( TodoModel::SummaryColumn ); mProxyModel->setFilterCaseSensitivity( Qt::CaseInsensitive ); mProxyModel->setSortRole( Qt::EditRole ); connect( mProxyModel, SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(onRowsInserted(QModelIndex,int,int)) ); if ( !mSidebarView ) { mQuickSearch = new TodoViewQuickSearch( calendar(), this ); mQuickSearch->setVisible( prefs->enableTodoQuickSearch() ); connect( mQuickSearch, SIGNAL(searchTextChanged(QString)), mProxyModel, SLOT(setFilterRegExp(QString)) ); connect( mQuickSearch, SIGNAL(searchTextChanged(QString)), SLOT(restoreViewState()) ); connect( mQuickSearch, SIGNAL(filterCategoryChanged(QStringList)), mProxyModel, SLOT(setCategoryFilter(QStringList)) ); connect( mQuickSearch, SIGNAL(filterCategoryChanged(QStringList)), SLOT(restoreViewState()) ); connect( mQuickSearch, SIGNAL(filterPriorityChanged(QStringList)), mProxyModel, SLOT(setPriorityFilter(QStringList)) ); connect( mQuickSearch, SIGNAL(filterPriorityChanged(QStringList)), SLOT(restoreViewState()) ); } mView = new TodoViewView( this ); mView->setModel( mProxyModel ); mView->setContextMenuPolicy( Qt::CustomContextMenu ); mView->setSortingEnabled( true ); mView->setAutoExpandDelay( 250 ); mView->setDragDropMode( QAbstractItemView::DragDrop ); mView->setExpandsOnDoubleClick( false ); mView->setEditTriggers( QAbstractItemView::SelectedClicked | QAbstractItemView::EditKeyPressed ); connect( mView->header(), SIGNAL(geometriesChanged()), SLOT(scheduleResizeColumns()) ); connect( mView, SIGNAL(visibleColumnCountChanged()), SLOT(resizeColumns()) ); TodoRichTextDelegate *richTextDelegate = new TodoRichTextDelegate( mView ); mView->setItemDelegateForColumn( TodoModel::SummaryColumn, richTextDelegate ); mView->setItemDelegateForColumn( TodoModel::DescriptionColumn, richTextDelegate ); TodoPriorityDelegate *priorityDelegate = new TodoPriorityDelegate( mView ); mView->setItemDelegateForColumn( TodoModel::PriorityColumn, priorityDelegate ); TodoDueDateDelegate *startDateDelegate = new TodoDueDateDelegate( mView ); mView->setItemDelegateForColumn( TodoModel::StartDateColumn, startDateDelegate ); TodoDueDateDelegate *dueDateDelegate = new TodoDueDateDelegate( mView ); mView->setItemDelegateForColumn( TodoModel::DueDateColumn, dueDateDelegate ); TodoCompleteDelegate *completeDelegate = new TodoCompleteDelegate( mView ); mView->setItemDelegateForColumn( TodoModel::PercentColumn, completeDelegate ); mCategoriesDelegate = new TodoCategoriesDelegate( mView ); mView->setItemDelegateForColumn( TodoModel::CategoriesColumn, mCategoriesDelegate ); connect( mView, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(contextMenu(QPoint)) ); connect( mView, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(itemDoubleClicked(QModelIndex)) ); connect( mView->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), this, SLOT(selectionChanged(QItemSelection,QItemSelection)) ); mQuickAdd = new TodoViewQuickAddLine( this ); mQuickAdd->setClearButtonShown( true ); mQuickAdd->setVisible( preferences()->enableQuickTodo() ); connect( mQuickAdd, SIGNAL(returnPressed(Qt::KeyboardModifiers)), this, SLOT(addQuickTodo(Qt::KeyboardModifiers)) ); mFullViewButton = 0; if ( !mSidebarView ) { mFullViewButton = new QToolButton( this ); mFullViewButton->setAutoRaise( true ); mFullViewButton->setCheckable( true ); mFullViewButton->setToolTip( i18nc( "@info:tooltip", "Display to-do list in a full window" ) ); mFullViewButton->setWhatsThis( i18nc( "@info:whatsthis", "Checking this option will cause the to-do view to use the full window." ) ); } mFlatViewButton = new QToolButton( this ); mFlatViewButton->setAutoRaise( true ); mFlatViewButton->setCheckable( true ); mFlatViewButton->setToolTip( i18nc( "@info:tooltip", "Display to-dos in flat list instead of a tree" ) ); mFlatViewButton->setWhatsThis( i18nc( "@info:whatsthis", "Checking this option will cause the to-dos to be displayed as a " "flat list instead of a hierarchical tree; the parental " "relationships are removed in the display." ) ); connect( mFlatViewButton, SIGNAL(toggled(bool)), SLOT(setFlatView(bool)) ); if ( mFullViewButton ) { connect( mFullViewButton, SIGNAL(toggled(bool)), SLOT(setFullView(bool)) ); } QGridLayout *layout = new QGridLayout( this ); layout->setMargin( 0 ); if ( !mSidebarView ) { layout->addWidget( mQuickSearch, 0, 0, 1, 2 ); } layout->addWidget( mView, 1, 0, 1, 2 ); layout->setRowStretch( 1, 1 ); layout->addWidget( mQuickAdd, 2, 0 ); // Dummy layout just to add a few px of right margin so the checkbox is aligned // with the QAbstractItemView's viewport. QHBoxLayout *dummyLayout = new QHBoxLayout(); dummyLayout->setContentsMargins( 0, 0, mView->frameWidth()/*right*/, 0 ); if ( !mSidebarView ) { QFrame *f = new QFrame( this ); f->setFrameShape( QFrame::VLine ); f->setFrameShadow( QFrame::Sunken ); dummyLayout->addWidget( f ); dummyLayout->addWidget( mFullViewButton ); } dummyLayout->addWidget( mFlatViewButton ); layout->addLayout( dummyLayout, 2, 1 ); setLayout( layout ); // ---------------- POPUP-MENUS ----------------------- mItemPopupMenu = new QMenu( this ); mItemPopupMenuItemOnlyEntries << mItemPopupMenu->addAction( i18nc( "@action:inmenu show the to-do", "&Show" ), this, SLOT(showTodo()) ); QAction *a = mItemPopupMenu->addAction( i18nc( "@action:inmenu edit the to-do", "&Edit..." ), this, SLOT(editTodo()) ); mItemPopupMenuReadWriteEntries << a; mItemPopupMenuItemOnlyEntries << a; mItemPopupMenu->addSeparator(); mItemPopupMenuItemOnlyEntries << mItemPopupMenu->addAction( cachedSmallIcon( QLatin1String("document-print") ), i18nc( "@action:inmenu print the to-do", "&Print..." ), this, SIGNAL(printTodo()) ); mItemPopupMenuItemOnlyEntries << mItemPopupMenu->addAction( cachedSmallIcon( QLatin1String("document-print-preview") ), i18nc( "@action:inmenu print preview the to-do", "Print Previe&w..." ), this, SIGNAL(printPreviewTodo()) ); mItemPopupMenu->addSeparator(); a = mItemPopupMenu->addAction( KIconLoader::global()->loadIcon( QLatin1String("edit-delete"), KIconLoader::NoGroup, KIconLoader::SizeSmall ), i18nc( "@action:inmenu delete the to-do", "&Delete" ), this, SLOT(deleteTodo()) ); mItemPopupMenuReadWriteEntries << a; mItemPopupMenuItemOnlyEntries << a; mItemPopupMenu->addSeparator(); mItemPopupMenu->addAction( KIconLoader::global()->loadIcon( QLatin1String("view-calendar-tasks"), KIconLoader::NoGroup, KIconLoader::SizeSmall ), i18nc( "@action:inmenu create a new to-do", "New &To-do..." ), this, SLOT(newTodo()) ); a = mItemPopupMenu->addAction( i18nc( "@action:inmenu create a new sub-to-do", "New Su&b-to-do..." ), this, SLOT(newSubTodo()) ); mItemPopupMenuReadWriteEntries << a; mItemPopupMenuItemOnlyEntries << a; mMakeTodoIndependent = mItemPopupMenu->addAction( i18nc( "@action:inmenu", "&Make this To-do Independent" ), this, SIGNAL(unSubTodoSignal()) ); mMakeSubtodosIndependent = mItemPopupMenu->addAction( i18nc( "@action:inmenu", "Make all Sub-to-dos &Independent" ), this, SIGNAL(unAllSubTodoSignal()) ); mItemPopupMenuItemOnlyEntries << mMakeTodoIndependent; mItemPopupMenuItemOnlyEntries << mMakeSubtodosIndependent; mItemPopupMenuReadWriteEntries << mMakeTodoIndependent; mItemPopupMenuReadWriteEntries << mMakeSubtodosIndependent; mItemPopupMenu->addSeparator(); a = mItemPopupMenu->addAction( KIconLoader::global()->loadIcon( QLatin1String("appointment-new"), KIconLoader::NoGroup, KIconLoader::SizeSmall ), i18n( "Create Event" ), this, SLOT(createEvent()) ); a->setObjectName(QLatin1String("createevent")); mItemPopupMenuReadWriteEntries << a; mItemPopupMenuItemOnlyEntries << a; a = mItemPopupMenu->addAction( KIconLoader::global()->loadIcon( QLatin1String("view-pim-notes"), KIconLoader::NoGroup, KIconLoader::SizeSmall ), i18n( "Create Note" ), this, SLOT(createNote()) ); a->setObjectName(QLatin1String("createnote")); mItemPopupMenuReadWriteEntries << a; mItemPopupMenuItemOnlyEntries << a; mItemPopupMenu->addSeparator(); mCopyPopupMenu = new KPIM::KDatePickerPopup( KPIM::KDatePickerPopup::NoDate | KPIM::KDatePickerPopup::DatePicker | KPIM::KDatePickerPopup::Words, QDate::currentDate(), this ); mCopyPopupMenu->setTitle( i18nc( "@title:menu", "&Copy To" ) ); connect( mCopyPopupMenu, SIGNAL(dateChanged(QDate)), SLOT(copyTodoToDate(QDate)) ); connect( mCopyPopupMenu, SIGNAL(dateChanged(QDate)), mItemPopupMenu, SLOT(hide()) ); mMovePopupMenu = new KPIM:: KDatePickerPopup( KPIM::KDatePickerPopup::NoDate | KPIM::KDatePickerPopup::DatePicker | KPIM::KDatePickerPopup::Words, QDate::currentDate(), this ); mMovePopupMenu->setTitle( i18nc( "@title:menu", "&Move To" ) ); connect( mMovePopupMenu, SIGNAL(dateChanged(QDate)), SLOT(setNewDate(QDate)) ); connect( mMovePopupMenu, SIGNAL(dateChanged(QDate)), mItemPopupMenu, SLOT(hide()) ); mItemPopupMenu->insertMenu( 0, mCopyPopupMenu ); mItemPopupMenu->insertMenu( 0, mMovePopupMenu ); mItemPopupMenu->addSeparator(); mItemPopupMenu->addAction( i18nc( "@action:inmenu delete completed to-dos", "Pur&ge Completed" ), this, SIGNAL(purgeCompletedSignal()) ); mPriorityPopupMenu = new QMenu( this ); mPriority[ mPriorityPopupMenu->addAction( i18nc( "@action:inmenu unspecified priority", "unspecified" ) ) ] = 0; mPriority[ mPriorityPopupMenu->addAction( i18nc( "@action:inmenu highest priority", "1 (highest)" ) ) ] = 1; mPriority[ mPriorityPopupMenu->addAction( i18nc( "@action:inmenu priority value=2", "2" ) ) ] = 2; mPriority[ mPriorityPopupMenu->addAction( i18nc( "@action:inmenu priority value=3", "3" ) ) ] = 3; mPriority[ mPriorityPopupMenu->addAction( i18nc( "@action:inmenu priority value=4", "4" ) ) ] = 4; mPriority[ mPriorityPopupMenu->addAction( i18nc( "@action:inmenu medium priority", "5 (medium)" ) ) ] = 5; mPriority[ mPriorityPopupMenu->addAction( i18nc( "@action:inmenu priority value=6", "6" ) ) ] = 6; mPriority[ mPriorityPopupMenu->addAction( i18nc( "@action:inmenu priority value=7", "7" ) ) ] = 7; mPriority[ mPriorityPopupMenu->addAction( i18nc( "@action:inmenu priority value=8", "8" ) ) ] = 8; mPriority[ mPriorityPopupMenu->addAction( i18nc( "@action:inmenu lowest priority", "9 (lowest)" ) ) ] = 9; connect( mPriorityPopupMenu, SIGNAL(triggered(QAction*)), SLOT(setNewPriority(QAction*)) ); mPercentageCompletedPopupMenu = new QMenu(this); for ( int i = 0; i <= 100; i+=10 ) { const QString label = QString::fromLatin1( "%1 %" ).arg( i ); mPercentage[mPercentageCompletedPopupMenu->addAction( label )] = i; } connect( mPercentageCompletedPopupMenu, SIGNAL(triggered(QAction*)), SLOT(setNewPercentage(QAction*)) ); setMinimumHeight( 50 ); // Initialize our proxy models setFlatView( preferences()->flatListTodo() ); setFullView( preferences()->fullViewTodo() ); updateConfig(); } TodoView::~TodoView() { saveViewState(); sModels->unregisterView( this ); if ( sModels->views.isEmpty() ) { delete sModels; sModels = 0; } } void TodoView::expandIndex( const QModelIndex &index ) { QModelIndex todoModelIndex = sModels->todoModel->mapFromSource( index ); Q_ASSERT( todoModelIndex.isValid() ); QModelIndex realIndex = mProxyModel->mapFromSource( todoModelIndex ); Q_ASSERT( realIndex.isValid() ); while ( realIndex.isValid() ) { mView->expand( realIndex ); realIndex = mProxyModel->parent( realIndex ); } } void TodoView::setCalendar( const Akonadi::ETMCalendar::Ptr &calendar ) { EventView::setCalendar( calendar ); if ( !mSidebarView ) { mQuickSearch->setCalendar( calendar ); } mCategoriesDelegate->setCalendar( calendar ); sModels->setCalendar( calendar ); restoreViewState(); } Akonadi::Item::List TodoView::selectedIncidences() const { Akonadi::Item::List ret; const QModelIndexList selection = mView->selectionModel()->selectedRows(); Q_FOREACH ( const QModelIndex &mi, selection ) { ret << mi.data ( TodoModel::TodoRole ).value(); } return ret; } DateList TodoView::selectedIncidenceDates() const { // The todo view only lists todo's. It's probably not a good idea to // return something about the selected todo here, because it has got // a couple of dates (creation, due date, completion date), and the // caller could not figure out what he gets. So just return an empty list. return DateList(); } void TodoView::saveLayout( KConfig *config, const QString &group ) const { KConfigGroup cfgGroup = config->group( group ); QHeaderView *header = mView->header(); QVariantList columnVisibility; QVariantList columnOrder; QVariantList columnWidths; for ( int i = 0; i < header->count(); ++i ) { columnVisibility << QVariant( !mView->isColumnHidden( i ) ); columnWidths << QVariant( header->sectionSize( i ) ); columnOrder << QVariant( header->visualIndex( i ) ); } cfgGroup.writeEntry( "ColumnVisibility", columnVisibility ); cfgGroup.writeEntry( "ColumnOrder", columnOrder ); cfgGroup.writeEntry( "ColumnWidths", columnWidths ); cfgGroup.writeEntry( "SortAscending", (int)header->sortIndicatorOrder() ); if ( header->isSortIndicatorShown() ) { cfgGroup.writeEntry( "SortColumn", header->sortIndicatorSection() ); } else { cfgGroup.writeEntry( "SortColumn", -1 ); } if ( !mSidebarView ) { preferences()->setFullViewTodo( mFullViewButton->isChecked() ); } preferences()->setFlatListTodo( mFlatViewButton->isChecked() ); } void TodoView::restoreLayout( KConfig *config, const QString &group, bool minimalDefaults ) { KConfigGroup cfgGroup = config->group( group ); QHeaderView *header = mView->header(); QVariantList columnVisibility = cfgGroup.readEntry( "ColumnVisibility", QVariantList() ); QVariantList columnOrder = cfgGroup.readEntry( "ColumnOrder", QVariantList() ); QVariantList columnWidths = cfgGroup.readEntry( "ColumnWidths", QVariantList() ); if ( columnVisibility.isEmpty() ) { // if config is empty then use default settings mView->hideColumn( TodoModel::RecurColumn ); mView->hideColumn( TodoModel::DescriptionColumn ); mView->hideColumn( TodoModel::CalendarColumn ); if ( minimalDefaults ) { mView->hideColumn( TodoModel::PriorityColumn ); mView->hideColumn( TodoModel::PercentColumn ); mView->hideColumn( TodoModel::DescriptionColumn ); mView->hideColumn( TodoModel::CategoriesColumn ); } // We don't have any incidences (content) yet, so we delay resizing QTimer::singleShot( 0, this, SLOT(resizeColumns()) ); } else { for ( int i = 0; i < header->count() && i < columnOrder.size() && i < columnWidths.size() && i < columnVisibility.size(); i++ ) { bool visible = columnVisibility[i].toBool(); int width = columnWidths[i].toInt(); int order = columnOrder[i].toInt(); header->resizeSection( i, width ); header->moveSection( header->visualIndex( i ), order ); if ( i != 0 && !visible ) { mView->hideColumn( i ); } } } int sortOrder = cfgGroup.readEntry( "SortAscending", (int)Qt::AscendingOrder ); int sortColumn = cfgGroup.readEntry( "SortColumn", -1 ); if ( sortColumn >= 0 ) { mView->sortByColumn( sortColumn, (Qt::SortOrder)sortOrder ); } mFlatViewButton->setChecked( cfgGroup.readEntry( "FlatView", false ) ); } void TodoView::setIncidenceChanger( Akonadi::IncidenceChanger *changer ) { EventView::setIncidenceChanger( changer ); sModels->todoModel->setIncidenceChanger( changer ); } void TodoView::showDates( const QDate &start, const QDate &end, const QDate & ) { // There is nothing to do here for the Todo View Q_UNUSED( start ); Q_UNUSED( end ); } void TodoView::showIncidences( const Akonadi::Item::List &incidenceList, const QDate &date ) { Q_UNUSED( incidenceList ); Q_UNUSED( date ); } void TodoView::updateView() { // View is always updated, it's connected to ETM. } void TodoView::changeIncidenceDisplay( const Akonadi::Item &, Akonadi::IncidenceChanger::ChangeType ) { // Don't do anything, model is connected to ETM, it's up to date } void TodoView::updateConfig() { Q_ASSERT( preferences() ); if ( !mSidebarView && mQuickSearch ) { mQuickSearch->setVisible( preferences()->enableTodoQuickSearch() ); } if ( mQuickAdd ) mQuickAdd->setVisible( preferences()->enableQuickTodo() ); updateView(); } void TodoView::clearSelection() { mView->selectionModel()->clearSelection(); } void TodoView::addTodo( const QString &summary, const Akonadi::Item &parentItem, const QStringList &categories ) { if ( !changer() || summary.trimmed().isEmpty() ) { return; } KCalCore::Todo::Ptr parent = CalendarSupport::todo( parentItem ); KCalCore::Todo::Ptr todo( new KCalCore::Todo ); todo->setSummary( summary.trimmed() ); todo->setOrganizer( Person::Ptr( new Person( CalendarSupport::KCalPrefs::instance()->fullName(), CalendarSupport::KCalPrefs::instance()->email() ) ) ); todo->setCategories( categories ); if ( parent && !parent->hasRecurrenceId() ) { todo->setRelatedTo( parent->uid() ); } Akonadi::Collection collection; // Use the same collection of the parent. if ( parentItem.isValid() ) { // Don't use parentColection() since it might be a virtual collection collection = calendar()->collection( parentItem.storageCollectionId() ); } changer()->createIncidence( todo, Akonadi::Collection(), this ); } void TodoView::addQuickTodo( Qt::KeyboardModifiers modifiers ) { if ( modifiers == Qt::NoModifier ) { /*const QModelIndex index = */ addTodo( mQuickAdd->text(), Akonadi::Item(), mProxyModel->categories() ); } else if ( modifiers == Qt::ControlModifier ) { QModelIndexList selection = mView->selectionModel()->selectedRows(); if ( selection.count() != 1 ) { kWarning() << "No to-do selected" << selection; return; } const QModelIndex idx = mProxyModel->mapToSource( selection[0] ); mView->expand( selection[0] ); const Akonadi::Item parent = sModels->todoModel->data( idx, Akonadi::EntityTreeModel::ItemRole ).value(); addTodo( mQuickAdd->text(), parent, mProxyModel->categories() ); } else { return; } mQuickAdd->setText( QString() ); } void TodoView::contextMenu( const QPoint &pos ) { const bool hasItem = mView->indexAt( pos ).isValid(); Incidence::Ptr incidencePtr; Q_FOREACH ( QAction *entry, mItemPopupMenuItemOnlyEntries ) { bool enable; if ( hasItem ) { const Akonadi::Item::List incidences = selectedIncidences(); if ( incidences.isEmpty() ) { enable = false; } else { Akonadi::Item item = incidences.first(); incidencePtr = CalendarSupport::incidence( item ); // Action isn't RO, it can change the incidence, "Edit" for example. const bool actionIsRw = mItemPopupMenuReadWriteEntries.contains( entry ); const bool incidenceIsRO = !calendar()->hasRight( item, Akonadi::Collection::CanChangeItem ); enable = hasItem && ( !actionIsRw || ( actionIsRw && !incidenceIsRO ) ); } } else { enable = false; } entry->setEnabled( enable ); } mCopyPopupMenu->setEnabled( hasItem ); mMovePopupMenu->setEnabled( hasItem ); if ( hasItem ) { if ( incidencePtr ) { const bool hasRecId = incidencePtr->hasRecurrenceId(); if ( calendar() ) { - mMakeSubtodosIndependent->setEnabled( !hasRecId && !calendar()->childItems( incidencePtr->uid() ).isEmpty() ); + mMakeSubtodosIndependent->setEnabled( !hasRecId && !calendar()->childItems( incidencePtr ).isEmpty() ); } mMakeTodoIndependent->setEnabled( !hasRecId && !incidencePtr->relatedTo().isEmpty() ); } switch ( mView->indexAt( pos ).column() ) { case TodoModel::PriorityColumn: mPriorityPopupMenu->popup( mView->viewport()->mapToGlobal( pos ) ); break; case TodoModel::PercentColumn: mPercentageCompletedPopupMenu->popup( mView->viewport()->mapToGlobal( pos ) ); break; case TodoModel::StartDateColumn: case TodoModel::DueDateColumn: mMovePopupMenu->popup( mView->viewport()->mapToGlobal( pos ) ); break; case TodoModel::CategoriesColumn: createCategoryPopupMenu()->popup( mView->viewport()->mapToGlobal( pos ) ); break; default: mItemPopupMenu->popup( mView->viewport()->mapToGlobal( pos ) ); break; } } else { mItemPopupMenu->popup( mView->viewport()->mapToGlobal( pos ) ); } } void TodoView::selectionChanged( const QItemSelection &selected, const QItemSelection &deselected ) { Q_UNUSED( deselected ); QModelIndexList selection = selected.indexes(); if ( selection.isEmpty() || !selection[0].isValid() ) { emit incidenceSelected( Akonadi::Item(), QDate() ); return; } const Akonadi::Item todoItem = selection[0].data ( TodoModel::TodoRole ).value(); if ( selectedIncidenceDates().isEmpty() ) { emit incidenceSelected( todoItem, QDate() ); } else { emit incidenceSelected( todoItem, selectedIncidenceDates().first() ); } } void TodoView::showTodo() { QModelIndexList selection = mView->selectionModel()->selectedRows(); if ( selection.size() != 1 ) { return; } const Akonadi::Item todoItem = selection[0].data ( TodoModel::TodoRole ).value(); emit showIncidenceSignal( todoItem ); } void TodoView::editTodo() { QModelIndexList selection = mView->selectionModel()->selectedRows(); if ( selection.size() != 1 ) { return; } const Akonadi::Item todoItem = selection[0].data ( TodoModel::TodoRole ).value(); emit editIncidenceSignal( todoItem, KDateTime() ); } void TodoView::deleteTodo() { QModelIndexList selection = mView->selectionModel()->selectedRows(); if ( selection.size() == 1 ) { const Akonadi::Item todoItem = selection[0].data ( TodoModel::TodoRole ).value(); if ( !changer()->deletedRecently( todoItem.id() ) ) { emit deleteIncidenceSignal( todoItem ); } } } void TodoView::newTodo() { emit newTodoSignal( QDate::currentDate().addDays( 7 ) ); } void TodoView::newSubTodo() { QModelIndexList selection = mView->selectionModel()->selectedRows(); if ( selection.size() == 1 ) { const Akonadi::Item todoItem = selection[0].data ( TodoModel::TodoRole ).value(); emit newSubTodoSignal( todoItem ); } else { // This never happens kWarning() << "Selection size isn't 1"; } } void TodoView::copyTodoToDate( const QDate &date ) { if ( !changer() ) { return; } QModelIndexList selection = mView->selectionModel()->selectedRows(); if ( selection.size() != 1 ) { return; } const QModelIndex origIndex = mProxyModel->mapToSource( selection[0] ); Q_ASSERT( origIndex.isValid() ); const Akonadi::Item origItem = sModels->todoModel->data( origIndex, Akonadi::EntityTreeModel::ItemRole ).value(); const KCalCore::Todo::Ptr orig = CalendarSupport::todo( origItem ); if ( !orig ) { return; } KCalCore::Todo::Ptr todo( orig->clone() ); todo->setUid( KCalCore::CalFormat::createUniqueId() ); KDateTime due = todo->dtDue(); due.setDate( date ); todo->setDtDue( due ); changer()->createIncidence( todo, Akonadi::Collection(), this ); } void TodoView::scheduleResizeColumns() { mResizeColumnsScheduled = true; mResizeColumnsTimer->start(); // restarts the timer if already active } void TodoView::itemDoubleClicked( const QModelIndex &index ) { if ( index.isValid() ) { QModelIndex summary = index.sibling( index.row(), TodoModel::SummaryColumn ); if ( summary.flags() & Qt::ItemIsEditable ) { editTodo(); } else { showTodo(); } } } QMenu *TodoView::createCategoryPopupMenu() { QMenu *tempMenu = new QMenu( this ); QModelIndexList selection = mView->selectionModel()->selectedRows(); if ( selection.size() != 1 ) { return tempMenu; } const Akonadi::Item todoItem = selection[0].data ( TodoModel::TodoRole ).value(); KCalCore::Todo::Ptr todo = CalendarSupport::todo( todoItem ); Q_ASSERT( todo ); const QStringList checkedCategories = todo->categories(); Akonadi::TagFetchJob *tagFetchJob = new Akonadi::TagFetchJob(this); connect(tagFetchJob, SIGNAL(result(KJob*)), this, SLOT(onTagsFetched(KJob*))); tagFetchJob->setProperty("menu", QVariant::fromValue(QPointer(tempMenu))); tagFetchJob->setProperty("checkedCategories", checkedCategories); connect( tempMenu, SIGNAL(triggered(QAction*)), SLOT(changedCategories(QAction*)) ); connect( tempMenu, SIGNAL(aboutToHide()), tempMenu, SLOT(deleteLater()) ); return tempMenu; } void TodoView::onTagsFetched(KJob *job) { if (job->error()) { kWarning() << "Failed to fetch tags " << job->errorString(); return; } Akonadi::TagFetchJob *fetchJob = static_cast(job); const QStringList checkedCategories = job->property("checkedCategories").toStringList(); QPointer menu = job->property("menu").value >(); if (menu) { Q_FOREACH (const Akonadi::Tag &tag, fetchJob->tags()) { const QString name = tag.name(); QAction *action = menu->addAction( name ); action->setCheckable( true ); action->setData(name); if ( checkedCategories.contains( name ) ) { action->setChecked( true ); } } } } void TodoView::setNewDate( const QDate &date ) { QModelIndexList selection = mView->selectionModel()->selectedRows(); if ( selection.size() != 1 ) { return; } const Akonadi::Item todoItem = selection[0].data ( TodoModel::TodoRole ).value(); KCalCore::Todo::Ptr todo = CalendarSupport::todo( todoItem ); Q_ASSERT( todo ); if ( calendar()->hasRight( todoItem, Akonadi::Collection::CanChangeItem ) ) { KCalCore::Todo::Ptr oldTodo( todo->clone() ); KDateTime dt( date ); if ( !todo->allDay() ) { dt.setTime( todo->dtDue().time() ); } todo->setDtDue( dt ); changer()->modifyIncidence( todoItem, oldTodo, this ); } else { kDebug() << "Item is readOnly"; } } void TodoView::setNewPercentage( QAction *action ) { QModelIndexList selection = mView->selectionModel()->selectedRows(); if ( selection.size() != 1 ) { return; } const Akonadi::Item todoItem = selection[0].data ( TodoModel::TodoRole ).value(); KCalCore::Todo::Ptr todo = CalendarSupport::todo( todoItem ); Q_ASSERT( todo ); if ( calendar()->hasRight( todoItem, Akonadi::Collection::CanChangeItem ) ) { KCalCore::Todo::Ptr oldTodo( todo->clone() ); int percentage = mPercentage.value( action ); if ( percentage == 100 ) { todo->setCompleted( KDateTime::currentLocalDateTime() ); todo->setPercentComplete( 100 ); } else { todo->setPercentComplete( percentage ); } if ( todo->recurs() && percentage == 100 ) { changer()->modifyIncidence( todoItem, oldTodo, this ); } else { changer()->modifyIncidence( todoItem, oldTodo, this ); } } else { kDebug() << "Item is read only"; } } void TodoView::setNewPriority( QAction *action ) { QModelIndexList selection = mView->selectionModel()->selectedRows(); if ( selection.size() != 1 ) { return; } const Akonadi::Item todoItem = selection[0].data ( TodoModel::TodoRole ).value(); KCalCore::Todo::Ptr todo = CalendarSupport::todo( todoItem ); if ( calendar()->hasRight( todoItem, Akonadi::Collection::CanChangeItem ) ) { KCalCore::Todo::Ptr oldTodo( todo->clone() ); todo->setPriority( mPriority[action] ); changer()->modifyIncidence( todoItem, oldTodo, this ); } } void TodoView::changedCategories( QAction *action ) { QModelIndexList selection = mView->selectionModel()->selectedRows(); if ( selection.size() != 1 ) { return; } const Akonadi::Item todoItem = selection[0].data ( TodoModel::TodoRole ).value(); KCalCore::Todo::Ptr todo = CalendarSupport::todo( todoItem ); Q_ASSERT( todo ); if ( calendar()->hasRight( todoItem, Akonadi::Collection::CanChangeItem ) ) { KCalCore::Todo::Ptr oldTodo( todo->clone() ); const QString cat = action->data().toString(); QStringList categories = todo->categories(); if ( categories.contains( cat ) ) { categories.removeAll( cat ); } else { categories.append( cat ); } categories.sort(); todo->setCategories( categories ); changer()->modifyIncidence( todoItem, oldTodo, this ); } else { kDebug() << "No active item, active item is read-only, or locking failed"; } } void TodoView::setFullView( bool fullView ) { if ( !mFullViewButton ) { return; } mFullViewButton->setChecked( fullView ); if ( fullView ) { mFullViewButton->setIcon( KIcon( QLatin1String("view-restore") ) ); } else { mFullViewButton->setIcon( KIcon( QLatin1String("view-fullscreen") ) ); } mFullViewButton->blockSignals( true ); // We block signals to avoid recursion; there are two TodoViews and // also mFullViewButton is synchronized. mFullViewButton->setChecked( fullView ); mFullViewButton->blockSignals( false ); preferences()->setFullViewTodo( fullView ); preferences()->writeConfig(); emit fullViewChanged( fullView ); } void TodoView::setFlatView( bool flatView, bool notifyOtherViews ) { if ( flatView ) { mFlatViewButton->setIcon( KIcon( QLatin1String("view-list-tree") ) ); } else { mFlatViewButton->setIcon( KIcon( QLatin1String("view-list-details") ) ); } if ( notifyOtherViews ) { sModels->setFlatView( flatView ); } } void TodoView::onRowsInserted( const QModelIndex &parent, int start, int end) { if ( start != end || !calendar() || !calendar()->entityTreeModel() ) return; QModelIndex idx = mView->model()->index( start, 0 ); // If the collection is currently being populated, we don't do anything QVariant v = idx.data( Akonadi::EntityTreeModel::ItemRole ); if ( !v.isValid() ) return; Akonadi::Item item = v.value(); if ( !item.isValid() ) return; const bool isPopulated = calendar()->entityTreeModel()->isCollectionPopulated( item.storageCollectionId() ); if ( !isPopulated ) return; // Case #1, adding an item that doesn't have parent: We select it if ( !parent.isValid() ) { QModelIndexList selection = mView->selectionModel()->selectedRows(); if ( selection.size() <= 1 ) { // don't destroy complex selections, not applicable now (only single // selection allowed), but for the future... int colCount = static_cast( TodoModel::ColumnCount ); mView->selectionModel()->select( QItemSelection( idx, mView->model()->index( start, colCount-1 ) ), QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows ); } return; } // Case 2: Adding an item that has a parent: we expand the parent if ( sModels->isFlatView() ) return; QModelIndex index = parent; mView->expand( index ); while ( index.parent().isValid() ) { mView->expand( index.parent() ); index = index.parent(); } } void TodoView::getHighlightMode( bool &highlightEvents, bool &highlightTodos, bool &highlightJournals ) { highlightTodos = preferences()->highlightTodos(); highlightEvents = !highlightTodos; highlightJournals = false; } bool TodoView::usesFullWindow() { return preferences()->fullViewTodo(); } void TodoView::resizeColumns() { mResizeColumnsScheduled = false; mView->resizeColumnToContents( TodoModel::StartDateColumn ); mView->resizeColumnToContents( TodoModel::DueDateColumn ); mView->resizeColumnToContents( TodoModel::PriorityColumn); mView->resizeColumnToContents( TodoModel::CalendarColumn); mView->resizeColumnToContents( TodoModel::RecurColumn); mView->resizeColumnToContents( TodoModel::PercentColumn); // We have 3 columns that should stretch: summary, description and categories. // Summary is always visible. const bool descriptionVisible = !mView->isColumnHidden(TodoModel::DescriptionColumn); const bool categoriesVisible = !mView->isColumnHidden(TodoModel::CategoriesColumn); // Calculate size of non-stretchable columns: int size = 0; for ( int i=0; iisColumnHidden(i) && i != TodoModel::SummaryColumn && i != TodoModel::DescriptionColumn && i != TodoModel::CategoriesColumn ) size += mView->columnWidth(i); } // Calculate the remaining space that we have for the stretchable columns int remainingSize = mView->header()->width() - size; // 100 for summary, 100 for description const int requiredSize = descriptionVisible ? 200 : 100; if ( categoriesVisible ) { const int categorySize = 100; mView->setColumnWidth( TodoModel::CategoriesColumn, categorySize ); remainingSize -= categorySize; } if ( remainingSize < requiredSize ) { // We have too little size ( that's what she...), so lets use an horizontal scrollbar and make these columns use whatever they need. mView->resizeColumnToContents( TodoModel::SummaryColumn ); mView->resizeColumnToContents( TodoModel::DescriptionColumn ); } else if ( descriptionVisible ) { mView->setColumnWidth( TodoModel::SummaryColumn, remainingSize / 2 ); mView->setColumnWidth( TodoModel::DescriptionColumn, remainingSize / 2 ); } else { mView->setColumnWidth( TodoModel::SummaryColumn, remainingSize ); } } void TodoView::restoreViewState() { if ( sModels->isFlatView() ) { return; } if ( sModels->todoTreeModel && !sModels->todoTreeModel->sourceModel() ) { return; } //QElapsedTimer timer; //timer.start(); delete mTreeStateRestorer; mTreeStateRestorer = new Akonadi::ETMViewStateSaver(); KConfigGroup group( KGlobal::activeComponent().config().data(), stateSaverGroup() ); mTreeStateRestorer->setView( mView ); mTreeStateRestorer->restoreState( group ); //kDebug() << "Took " << timer.elapsed(); } QString TodoView::stateSaverGroup() const { QString str = QLatin1String( "TodoTreeViewState" ); if ( mSidebarView ) { str += QLatin1Char( 'S' ); } return str; } void TodoView::saveViewState() { Akonadi::ETMViewStateSaver treeStateSaver; KConfigGroup group( preferences()->config(), stateSaverGroup() ); treeStateSaver.setView( mView ); treeStateSaver.saveState( group ); } void TodoView::resizeEvent( QResizeEvent *event ) { EventViews::EventView::resizeEvent( event ); scheduleResizeColumns(); } void TodoView::createEvent() { QModelIndexList selection = mView->selectionModel()->selectedRows(); if ( selection.size() != 1 ) { return; } const Akonadi::Item todoItem = selection[0].data ( TodoModel::TodoRole ).value(); emit createEvent( todoItem ); } void TodoView::createNote() { QModelIndexList selection = mView->selectionModel()->selectedRows(); if ( selection.size() != 1 ) { return; } const Akonadi::Item todoItem = selection[0].data ( TodoModel::TodoRole ).value(); emit createNote( todoItem ); } diff --git a/calendarviews/agenda/viewcalendar.cpp b/calendarviews/viewcalendar.cpp similarity index 67% rename from calendarviews/agenda/viewcalendar.cpp rename to calendarviews/viewcalendar.cpp index 8aeb095b91..f4cbe73b7b 100644 --- a/calendarviews/agenda/viewcalendar.cpp +++ b/calendarviews/viewcalendar.cpp @@ -1,196 +1,223 @@ #include "viewcalendar.h" -#include "agendaview.h" +#include "eventview.h" #include "helper.h" +#include #include using namespace EventViews; ViewCalendar::~ViewCalendar() { } MultiViewCalendar::~MultiViewCalendar() { } -KCalCore::Calendar::Ptr MultiViewCalendar::getCalendar() const -{ - return KCalCore::Calendar::Ptr(); -} - KCalCore::Incidence::List MultiViewCalendar::incidences() const { KCalCore::Incidence::List list; foreach(const ViewCalendar::Ptr &cal, mSubCalendars) { if (cal->getCalendar()) { list += cal->getCalendar()->incidences(); } } return list; } +KCalCore::Journal::List MultiViewCalendar::journals(const QDate &date) const +{ + KCalCore::Journal::List list; + foreach(const ViewCalendar::Ptr &cal, mSubCalendars) { + if (cal->getCalendar()) { + list += cal->getCalendar()->journals(date); + } + } + return list; +} + int MultiViewCalendar::calendars() const { return mSubCalendars.size(); } ViewCalendar::Ptr MultiViewCalendar::findCalendar(const KCalCore::Incidence::Ptr &incidence) const { foreach(const ViewCalendar::Ptr &cal, mSubCalendars) { if (cal->isValid(incidence)) { return cal; } } return ViewCalendar::Ptr(); } -ViewCalendar::Ptr MultiViewCalendar::findCalendar(const QString &incidenceIdentifier) const -{ - foreach(const ViewCalendar::Ptr &cal, mSubCalendars) { - if (cal->isValid(incidenceIdentifier)) { - return cal; - } - } - return ViewCalendar::Ptr(); -} - void MultiViewCalendar::addCalendar(const ViewCalendar::Ptr &calendar) { if (!mSubCalendars.contains(calendar)) { mSubCalendars.append(calendar); } } void MultiViewCalendar::setETMCalendar(const Akonadi::ETMCalendar::Ptr &calendar) { if (!mETMCalendar) { mETMCalendar = AkonadiViewCalendar::Ptr(new AkonadiViewCalendar); - mETMCalendar->mAgendaView = mAgendaView; + mETMCalendar->mEventView = mEventView; } mETMCalendar->mCalendar = calendar; addCalendar(mETMCalendar); } +Akonadi::ETMCalendar::Ptr MultiViewCalendar::etmCalendar() const +{ + if (mETMCalendar) { + return mETMCalendar->mCalendar; + } else { + return Akonadi::ETMCalendar::Ptr(); + } +} + + QString MultiViewCalendar::displayName(const KCalCore::Incidence::Ptr &incidence) const { ViewCalendar::Ptr cal = findCalendar(incidence); if (cal) { return cal->displayName(incidence); } return QString(); } QString MultiViewCalendar::iconForIncidence(const KCalCore::Incidence::Ptr &incidence) const { ViewCalendar::Ptr cal = findCalendar(incidence); if (cal) { return cal->iconForIncidence(incidence); } return QString(); } bool MultiViewCalendar::isValid(const KCalCore::Incidence::Ptr &incidence) const { ViewCalendar::Ptr cal = findCalendar(incidence); return cal; } -bool MultiViewCalendar::isValid(const QString &incidenceIdentifier) const -{ - ViewCalendar::Ptr cal = findCalendar(incidenceIdentifier); - return cal; -} - QColor MultiViewCalendar::resourceColor(const KCalCore::Incidence::Ptr &incidence) const { ViewCalendar::Ptr cal = findCalendar(incidence); if (cal) { return cal->resourceColor(incidence); } return QColor(); } +QString MultiViewCalendar::uid(const KCalCore::Incidence::Ptr &incidence) const +{ + ViewCalendar::Ptr cal = findCalendar(incidence); + if (cal) { + return cal->uid(incidence); + } else if (incidence) { + kWarning() << "Unknown incidence for MultiViewCalendar" << incidence->uid(); + } + //using it without a valid incidence don't make sense, so ASSERT + Q_ASSERT(incidence); + return QString(); +} + + Akonadi::Item MultiViewCalendar::item(const KCalCore::Incidence::Ptr &incidence) const { if (mETMCalendar->isValid(incidence)) { return mETMCalendar->item(incidence); } return Akonadi::Item(); } -AkonadiViewCalendar::~AkonadiViewCalendar() +QList< ViewCalendar::Ptr > MultiViewCalendar::subCalendars() const { + return mSubCalendars; } -bool AkonadiViewCalendar::isValid(const KCalCore::Incidence::Ptr &incidence) const -{ - if (!mCalendar) { - return false; - } - if (item(incidence).isValid()) { - return true; - } - return false; +AkonadiViewCalendar::~AkonadiViewCalendar() +{ } -bool AkonadiViewCalendar::isValid(const QString &incidenceIdentifier) const +bool AkonadiViewCalendar::isValid(const KCalCore::Incidence::Ptr &incidence) const { if (!mCalendar) { return false; } - return !mCalendar->incidence(incidenceIdentifier).isNull(); + return mCalendar->isValid(incidence); } Akonadi::Item AkonadiViewCalendar::item(const KCalCore::Incidence::Ptr &incidence) const { if (!mCalendar || !incidence) { return Akonadi::Item(); } bool ok = false; + //FIXME Akonadi::Item::Id id = incidence->customProperty("VOLATILE", "AKONADI-ID").toLongLong(&ok); if (id == -1 || !ok) { id = mCalendar->item(incidence).id(); if (id == -1) { // Ok, we really don't know the ID, give up. kDebug() << "Item is invalid. uid = " << incidence->instanceIdentifier(); return Akonadi::Item(); } - return mCalendar->item(incidence->instanceIdentifier()); + return mCalendar->item(incidence); } return mCalendar->item(id); } QString AkonadiViewCalendar::displayName(const KCalCore::Incidence::Ptr &incidence) const { return CalendarSupport::displayName( mCalendar.data(), item(incidence).parentCollection() ); } QColor AkonadiViewCalendar::resourceColor(const KCalCore::Incidence::Ptr &incidence) const { - return EventViews::resourceColor( item(incidence), mAgendaView->preferences() ); + return EventViews::resourceColor( item(incidence), mEventView->preferences() ); +} + +QString AkonadiViewCalendar::uid(const KCalCore::Incidence::Ptr &incidence) const +{ + return mCalendar->uniqueInstanceIdentifier(incidence); } QString AkonadiViewCalendar::iconForIncidence(const KCalCore::Incidence::Ptr &incidence) const { - return mAgendaView->iconForItem(item(incidence)); + QString iconName; + Akonadi::Collection collection = item(incidence).parentCollection(); + while ( collection.parentCollection().isValid() && + collection.parentCollection() != Akonadi::Collection::root() ) { + collection = mCalendar->collection( collection.parentCollection().id() ); + } + + if ( collection.isValid() && collection.hasAttribute() ) { + iconName = collection.attribute()->iconName(); + } + + return iconName; } + KDateTime::Spec AkonadiViewCalendar::timeSpec() const { return mCalendar->timeSpec(); } KCalCore::Calendar::Ptr AkonadiViewCalendar::getCalendar() const { return mCalendar; } diff --git a/calendarviews/agenda/viewcalendar.h b/calendarviews/viewcalendar.h similarity index 84% rename from calendarviews/agenda/viewcalendar.h rename to calendarviews/viewcalendar.h index afa39a684d..145ebbae2e 100644 --- a/calendarviews/agenda/viewcalendar.h +++ b/calendarviews/viewcalendar.h @@ -1,107 +1,109 @@ /* Copyright (c) 2014 Sandro Knauß This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #ifndef EVENTVIEWS_VIEWCALENDAR_H #define EVENTVIEWS_VIEWCALENDAR_H #include "eventviews_export.h" #include #include #include +#include #include #include namespace EventViews { -class AgendaView; +class EventView; class EVENTVIEWS_EXPORT ViewCalendar { public: typedef QSharedPointer Ptr; virtual ~ViewCalendar(); virtual bool isValid(const KCalCore::Incidence::Ptr &incidence) const=0; - virtual bool isValid(const QString &incidenceIdentifier) const=0; virtual QString displayName(const KCalCore::Incidence::Ptr &incidence) const=0; - virtual QColor resourceColor(const KCalCore::Incidence::Ptr &incidence) const=0; - virtual QString iconForIncidence(const KCalCore::Incidence::Ptr &incidence) const=0; + virtual QColor resourceColor(const KCalCore::Incidence::Ptr &incidence) const = 0; + virtual QString iconForIncidence(const KCalCore::Incidence::Ptr &incidence) const = 0; + virtual QString uid(const KCalCore::Incidence::Ptr &incidence) const = 0; - virtual KCalCore::Calendar::Ptr getCalendar() const=0; + virtual KCalCore::Calendar::Ptr getCalendar() const = 0; }; class EVENTVIEWS_EXPORT AkonadiViewCalendar: public ViewCalendar { public: typedef QSharedPointer Ptr; virtual ~AkonadiViewCalendar(); virtual bool isValid(const KCalCore::Incidence::Ptr &incidence) const; - virtual bool isValid(const QString &incidenceIdentifier) const; virtual QString displayName(const KCalCore::Incidence::Ptr &incidence) const; virtual QColor resourceColor(const KCalCore::Incidence::Ptr &incidence) const; virtual QString iconForIncidence(const KCalCore::Incidence::Ptr &incidence) const; virtual Akonadi::Item item(const KCalCore::Incidence::Ptr &incidence) const; + virtual QString uid(const KCalCore::Incidence::Ptr &incidence) const; virtual KCalCore::Calendar::Ptr getCalendar() const; KDateTime::Spec timeSpec() const; Akonadi::ETMCalendar::Ptr mCalendar; - AgendaView *mAgendaView; + EventView *mEventView; }; class EVENTVIEWS_EXPORT MultiViewCalendar { public: typedef QSharedPointer Ptr; virtual ~MultiViewCalendar(); ViewCalendar::Ptr findCalendar(const KCalCore::Incidence::Ptr &incidence) const; - ViewCalendar::Ptr findCalendar(const QString &incidenceIdentifier) const; virtual bool isValid(const KCalCore::Incidence::Ptr &incidence) const; - virtual bool isValid(const QString &incidenceIdentifier) const; virtual QString displayName(const KCalCore::Incidence::Ptr &incidence) const; virtual QColor resourceColor(const KCalCore::Incidence::Ptr &incidence) const; virtual QString iconForIncidence(const KCalCore::Incidence::Ptr &incidence) const; virtual Akonadi::Item item(const KCalCore::Incidence::Ptr &incidence) const; + virtual QString uid(const KCalCore::Incidence::Ptr &incidence) const; void addCalendar(const ViewCalendar::Ptr &calendar); void setETMCalendar(const Akonadi::ETMCalendar::Ptr &calendar); + Akonadi::ETMCalendar::Ptr etmCalendar() const; int calendars() const; - KCalCore::Calendar::Ptr getCalendar() const; + QList subCalendars() const; KCalCore::Incidence::List incidences() const; + KCalCore::Journal::List journals(const QDate &date) const; - AgendaView *mAgendaView; + EventView *mEventView; AkonadiViewCalendar::Ptr mETMCalendar; QList mSubCalendars; }; } -#endif \ No newline at end of file +#endif diff --git a/calendarviews/whatsnext/whatsnextview.cpp b/calendarviews/whatsnext/whatsnextview.cpp index d64da7d83d..361a1cce7d 100644 --- a/calendarviews/whatsnext/whatsnextview.cpp +++ b/calendarviews/whatsnext/whatsnextview.cpp @@ -1,332 +1,333 @@ /* This file is part of KOrganizer. Copyright (c) 2001 Cornelius Schumacher This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #include "whatsnextview.h" #include #include #include #include #include #include using namespace EventViews; void WhatsNextTextBrowser::setSource( const QUrl &name ) { QString uri = name.toString(); if ( uri.startsWith( QLatin1String( "event:" ) ) ) { emit showIncidence( uri ); } else if ( uri.startsWith( QLatin1String( "todo:" ) ) ) { emit showIncidence( uri ); } else { KTextBrowser::setSource( uri ); } } WhatsNextView::WhatsNextView( QWidget *parent ) : EventView( parent ) { mView = new WhatsNextTextBrowser( this ); connect( mView, SIGNAL(showIncidence(QString)), SLOT(showIncidence(QString)) ); QBoxLayout *topLayout = new QVBoxLayout( this ); topLayout->addWidget(mView); } WhatsNextView::~WhatsNextView() { } int WhatsNextView::currentDateCount() const { return mStartDate.daysTo( mEndDate ); } void WhatsNextView::updateView() { KIconLoader kil( QLatin1String("korganizer") ); QString ipath; kil.loadIcon( QLatin1String("office-calendar"), KIconLoader::NoGroup, 32, KIconLoader::DefaultState, QStringList(), &ipath ); mText = QLatin1String("\n"); mText += QLatin1String("\n\n

"); mText += QLatin1String(""); mText += QLatin1String(" "); mText += i18n( "What's Next?" ) + QLatin1String("

"); mText += QLatin1String("
"); mText += QLatin1String("

"); if ( mStartDate.daysTo( mEndDate ) < 1 ) { mText += KGlobal::locale()->formatDate( mStartDate ); } else { mText += i18nc( "date from - to", "%1 - %2", KGlobal::locale()->formatDate( mStartDate ), KGlobal::locale()->formatDate( mEndDate ) ); } mText+=QLatin1String("

\n"); KCalCore::Event::List events; KDateTime::Spec timeSpec = CalendarSupport::KCalPrefs::instance()->timeSpec(); events = calendar()->events( mStartDate, mEndDate, timeSpec, false ); events = calendar()->sortEvents( events, KCalCore::EventSortStartDate, KCalCore::SortDirectionAscending ); if ( events.count() > 0 ) { mText += QLatin1String("

"); kil.loadIcon( QLatin1String("view-calendar-day"), KIconLoader::NoGroup, 22, KIconLoader::DefaultState, QStringList(), &ipath ); mText += QLatin1String("

"); mText += i18n( "Events:" ) + QLatin1String("

\n"); mText += QLatin1String("\n"); Q_FOREACH( const KCalCore::Event::Ptr &ev, events ) { if ( !ev->recurs() ) { appendEvent( ev ); } else { KCalCore::Recurrence *recur = ev->recurrence(); int duration = ev->dtStart().secsTo( ev->dtEnd() ); KDateTime start = recur->getPreviousDateTime( KDateTime( mStartDate, QTime(), timeSpec ) ); KDateTime end = start.addSecs( duration ); KDateTime endDate( mEndDate, QTime( 23, 59, 59 ), timeSpec ); if ( end.date() >= mStartDate ) { appendEvent( ev, start.dateTime(), end.dateTime() ); } KCalCore::DateTimeList times = recur->timesInInterval( start, endDate ); int count = times.count(); if ( count > 0 ) { int i = 0; if ( times[0] == start ) { ++i; // start has already been appended } if ( !times[count - 1].isValid() ) { --count; // list overflow } for ( ; i < count && times[i].date() <= mEndDate; ++i ) { appendEvent( ev, times[i].dateTime() ); } } } } mText += QLatin1String("
\n"); } mTodos.clear(); KCalCore::Todo::List todos = calendar()->todos( KCalCore::TodoSortDueDate, KCalCore::SortDirectionAscending ); if ( todos.count() > 0 ) { kil.loadIcon( QLatin1String("view-calendar-tasks"), KIconLoader::NoGroup, 22, KIconLoader::DefaultState, QStringList(), &ipath ); mText += QLatin1String("

"); mText += i18n( "To-dos:" ) + QLatin1String("

\n"); mText += QLatin1String("
    \n"); Q_FOREACH( const KCalCore::Todo::Ptr &todo, todos ) { if ( !todo->isCompleted() && todo->hasDueDate() && todo->dtDue().date() <= mEndDate ) { appendTodo( todo ); } } bool gotone = false; int priority = 1; while ( !gotone && priority <= 9 ) { Q_FOREACH( const KCalCore::Todo::Ptr &todo, todos ) { if ( !todo->isCompleted() && ( todo->priority() == priority ) ) { appendTodo( todo ); gotone = true; } } priority++; } mText += QLatin1String("
\n"); } QStringList myEmails( CalendarSupport::KCalPrefs::instance()->allEmails() ); int replies = 0; events = calendar()->events( QDate::currentDate(), QDate( 2975, 12, 6 ), timeSpec ); Q_FOREACH( const KCalCore::Event::Ptr &ev, events ) { KCalCore::Attendee::Ptr me = ev->attendeeByMails( myEmails ); if ( me != 0 ) { if ( me->status() == KCalCore::Attendee::NeedsAction && me->RSVP() ) { if ( replies == 0 ) { mText += QLatin1String("

"); kil.loadIcon( QLatin1String("mail-reply-sender"), KIconLoader::NoGroup, 22, KIconLoader::DefaultState, QStringList(), &ipath ); mText += QLatin1String("

"); mText += i18n( "Events and to-dos that need a reply:" ) + QLatin1String("

\n"); mText += QLatin1String("\n"); } replies++; appendEvent( ev ); } } } todos = calendar()->todos(); Q_FOREACH( const KCalCore::Todo::Ptr &to, todos ) { KCalCore::Attendee::Ptr me = to->attendeeByMails( myEmails ); if ( me != 0 ) { if ( me->status() == KCalCore::Attendee::NeedsAction && me->RSVP() ) { if ( replies == 0 ) { mText += QLatin1String("

"); kil.loadIcon( QLatin1String("mail-reply-sender"), KIconLoader::NoGroup, 22, KIconLoader::DefaultState, QStringList(), &ipath ); mText += QLatin1String("

"); mText += i18n( "Events and to-dos that need a reply:" ) + QLatin1String("

\n"); mText += QLatin1String("
\n"); } replies++; appendEvent( to ); } } } if ( replies > 0 ) { mText += QLatin1String("
\n"); } mText += QLatin1String("
\n"); mView->setText(mText); } void WhatsNextView::showDates( const QDate &start, const QDate &end, const QDate & ) { mStartDate = start; mEndDate = end; updateView(); } void WhatsNextView::showIncidences( const Akonadi::Item::List &incidenceList, const QDate &date ) { Q_UNUSED( incidenceList ); Q_UNUSED( date ); } void WhatsNextView::changeIncidenceDisplay( const Akonadi::Item &, Akonadi::IncidenceChanger::ChangeType ) { updateView(); } void WhatsNextView::appendEvent( const KCalCore::Incidence::Ptr &incidence, const QDateTime &start, const QDateTime &end ) { mText += QLatin1String(""); if ( const KCalCore::Event::Ptr event = incidence.dynamicCast() ) { KDateTime::Spec timeSpec = CalendarSupport::KCalPrefs::instance()->timeSpec(); KDateTime starttime( start, timeSpec ); if ( !starttime.isValid() ) { starttime = event->dtStart(); } KDateTime endtime( end, timeSpec ); if ( !endtime.isValid() ) { endtime = starttime.addSecs( event->dtStart().secsTo( event->dtEnd() ) ); } if ( starttime.date().daysTo( endtime.date() ) >= 1 ) { mText += i18nc( "date from - to", "%1 - %2", KGlobal::locale()->formatDateTime( starttime.toTimeSpec( CalendarSupport::KCalPrefs::instance()->timeSpec() ) ), KGlobal::locale()->formatDateTime( endtime.toTimeSpec( CalendarSupport::KCalPrefs::instance()->timeSpec() ) ) ); } else { mText += i18nc( "date, from - to", "%1, %2 - %3", KGlobal::locale()->formatDate( starttime.toTimeSpec( CalendarSupport::KCalPrefs::instance()->timeSpec() ).date(), KLocale::ShortDate ), KGlobal::locale()->formatTime( starttime.toTimeSpec( CalendarSupport::KCalPrefs::instance()->timeSpec() ).time() ), KGlobal::locale()->formatTime( endtime.toTimeSpec( CalendarSupport::KCalPrefs::instance()->timeSpec() ).time() ) ); } } mText += QLatin1String("type() == KCalCore::Incidence::TypeEvent ) { mText += QLatin1String("href=\"event:"); } if ( incidence->type() == KCalCore::Incidence::TypeTodo ) { mText += QLatin1String("href=\"todo:"); } mText += incidence->uid() + QLatin1String("\">"); mText += incidence->summary(); mText += QLatin1String("\n"); } void WhatsNextView::appendTodo( const KCalCore::Incidence::Ptr &incidence ) { Akonadi::Item aitem = calendar()->item( incidence ); if ( mTodos.contains( aitem ) ) { return; } mTodos.append( aitem ); - mText += QLatin1String("
  • uid() + QLatin1String("\">"); + mText += QLatin1String("
  • uid(incidence) + QLatin1String("\">"); mText += incidence->summary(); mText += QLatin1String(""); if ( const KCalCore::Todo::Ptr todo = CalendarSupport::todo( aitem ) ) { if ( todo->hasDueDate() ) { mText += i18nc( "to-do due date", " (Due: %1)", KCalUtils::IncidenceFormatter::dateTimeToString( todo->dtDue(), todo->allDay() ) ); } } mText += QLatin1String("
  • \n"); } void WhatsNextView::showIncidence( const QString &uid ) { Akonadi::Item item; Akonadi::ETMCalendar::Ptr cal = calendar(); if ( !cal ) { return; } - if ( uid.startsWith( QLatin1String( "event:" ) ) ) { - item = cal->item( uid.mid( 6 ) ); - } else if ( uid.startsWith( QLatin1String( "todo:" ) ) ) { - item = cal->item( uid.mid( 5 ) ); - } + //FIXME + // if ( uid.startsWith( QLatin1String( "event:" ) ) ) { + // item = cal->item( uid.mid( 6 ) ); + // } else if ( uid.startsWith( QLatin1String( "todo:" ) ) ) { + // item = cal->item( uid.mid( 5 ) ); + // } if ( item.isValid() ) { emit showIncidenceSignal( item ); } } diff --git a/incidenceeditor-ng/resourcemanagement.cpp b/incidenceeditor-ng/resourcemanagement.cpp index ff36677a74..587cc47bb8 100644 --- a/incidenceeditor-ng/resourcemanagement.cpp +++ b/incidenceeditor-ng/resourcemanagement.cpp @@ -1,297 +1,301 @@ /* * Copyright 2014 Sandro Knauß * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ //http://stackoverflow.com/questions/18831242/qt-start-editing-of-cell-after-one-click #include "resourcemanagement.h" #include "ui_resourcemanagement.h" #include "resourcemodel.h" #include "freebusymodel/freebusyitem.h" #include "freebusymodel/freebusycalendar.h" #include "ldaputils.h" #include "freebusyganttproxymodel.h" #include -#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace IncidenceEditorNG; class FreebusyViewCalendar : public EventViews::ViewCalendar { public: virtual ~FreebusyViewCalendar() {}; virtual bool isValid(const KCalCore::Incidence::Ptr &incidence) const { return isValid(incidence->uid()); } virtual bool isValid(const QString &incidenceIdentifier) const { return incidenceIdentifier.startsWith("fb-"); } virtual QString displayName(const KCalCore::Incidence::Ptr &incidence) const { Q_UNUSED(incidence); return QLatin1String("Freebusy"); } virtual QColor resourceColor(const KCalCore::Incidence::Ptr &incidence) const { bool ok = false; int status = incidence->customProperty("FREEBUSY", "STATUS").toInt(&ok); if (!ok) { return QColor("#555"); } switch (status) { case KCalCore::FreeBusyPeriod::Busy: return QColor("#f00"); case KCalCore::FreeBusyPeriod::BusyTentative: case KCalCore::FreeBusyPeriod::BusyUnavailable: return QColor("#f70"); case KCalCore::FreeBusyPeriod::Free: return QColor("#0f0"); default: return QColor("#555"); } } + virtual QString uid(const KCalCore::Incidence::Ptr &incidence) const + { + return incidence->uid(); + } + virtual QString iconForIncidence(const KCalCore::Incidence::Ptr &incidence) const { return QString(); } virtual KCalCore::Calendar::Ptr getCalendar() const { return mCalendar; } KCalCore::Calendar::Ptr mCalendar; }; ResourceManagement::ResourceManagement() { setButtonText(KDialog::Ok, i18nc("@action:button add resource to attendeelist", "Book resource")); mUi = new Ui_resourceManagement; QWidget *w = new QWidget( this ); mUi->setupUi( w ); setMainWidget( w ); QVariantList list; mModel = new FreeBusyItemModel(this); mFreebusyCalendar.setModel(mModel); mAgendaView = new EventViews::AgendaView(QDate(), QDate(), false, false); FreebusyViewCalendar *fbCalendar = new FreebusyViewCalendar(); fbCalendar->mCalendar = mFreebusyCalendar.calendar(); mFbCalendar = EventViews::ViewCalendar::Ptr(fbCalendar); mAgendaView->addCalendar(mFbCalendar); mUi->resourceCalender->addWidget( mAgendaView ); QStringList attrs; attrs << QLatin1String("cn") << QLatin1String("mail") << QLatin1String("owner") << QLatin1String("givenname") << QLatin1String("sn") << QLatin1String("kolabDescAttribute") << QLatin1String("description"); ResourceModel *resourcemodel = new ResourceModel(attrs); mUi->treeResults->setModel(resourcemodel); // This doesn't work till now :( -> that's why i use the click signal mUi->treeResults->setSelectionMode(QAbstractItemView::SingleSelection); selectionModel = mUi->treeResults->selectionModel(); connect(mUi->resourceSearch, SIGNAL(textChanged(const QString&)), SLOT(slotStartSearch(const QString&))); connect(mUi->treeResults, SIGNAL(clicked(const QModelIndex &)), SLOT(slotShowDetails(const QModelIndex &))); connect(resourcemodel,SIGNAL(layoutChanged()),SLOT(slotLayoutChanged())); } ResourceManagement::~ResourceManagement() { delete mModel; } ResourceItem::Ptr ResourceManagement::selectedItem() const { return mSelectedItem; } void ResourceManagement::slotStartSearch(const QString &text) { ((ResourceModel*)mUi->treeResults->model())->startSearch(text); } void ResourceManagement::slotShowDetails(const QModelIndex & current) { ResourceItem::Ptr item = current.model()->data(current, ResourceModel::Resource).value(); mSelectedItem = item; showDetails(item->ldapObject(), item->ldapClient()); } void ResourceManagement::showDetails(const KLDAP::LdapObject &obj, const KLDAP::LdapClient &client) { // Clean up formDetails QLayoutItem *child; while ((child = mUi->formDetails->takeAt(0)) != 0) { delete child->widget(); delete child; } mUi->groupOwner->setHidden(true); // Fill formDetails with data foreach(const QString & key, obj.attributes().keys()) { if (key == QLatin1String("objectClass") || key == QLatin1String("email")) { continue; } else if (key == QLatin1String("owner")) { QStringList attrs; attrs << QLatin1String("cn") << QLatin1String("mail") << QLatin1String("mobile") << QLatin1String("telephoneNumber") << QLatin1String("kolabDescAttribute") << QLatin1String("description"); mOwnerItem = ResourceItem::Ptr(new ResourceItem(KLDAP::LdapDN(QString::fromUtf8(obj.attributes().value(key).at(0))), attrs, client)); connect(mOwnerItem.data(), SIGNAL(searchFinished()), SLOT(slotOwnerSearchFinished())); mOwnerItem->startSearch(); continue; } QStringList list; foreach(const QByteArray & value, obj.attributes().value(key)) { list << QString::fromUtf8(value); } if (key == QLatin1String("kolabDescAttribute")) { QJson::Parser parser; foreach(const QString &attr, list) { bool ok; QMap map = parser.parse(attr.toUtf8(), &ok).toMap(); foreach(const QString &pKey, map.keys()) { QString value; if (map.value(pKey).type() == QVariant::Bool) { value = map.value(pKey).toBool() ? i18n("yes") : i18n("no"); } else { value = map.value(pKey).toString(); } mUi->formDetails->addRow(translateKolabLDAPAttributeForDisplay(pKey), new QLabel(value)); } } } else { mUi->formDetails->addRow(translateLDAPAttributeForDisplay(key), new QLabel(list.join("\n"))); } } QString name = QString::fromUtf8(obj.attributes().value("cn")[0]); QString email = QString::fromUtf8(obj.attributes().value("mail")[0]); KCalCore::Attendee::Ptr attendee(new KCalCore::Attendee(name, email)); FreeBusyItem::Ptr freebusy( new FreeBusyItem( attendee, this )); mModel->clear(); mModel->addItem(freebusy); } void ResourceManagement::slotLayoutChanged() { for(int i = 1; i < mUi->treeResults->model()->columnCount(QModelIndex());i++) { mUi->treeResults->setColumnHidden(i, true); } } void ResourceManagement::slotOwnerSearchFinished() { // Clean up formDetails QLayoutItem *child; while ((child = mUi->formOwner->takeAt(0)) != 0) { delete child->widget(); delete child; } mUi->groupOwner->setHidden(false); const KLDAP::LdapObject &obj = mOwnerItem->ldapObject(); foreach(const QString & key, obj.attributes().keys()) { if (key == QLatin1String("objectClass") || key == QLatin1String("owner") || key == QLatin1String("givenname") || key == QLatin1String("sn")) { continue; } QStringList list; foreach(const QByteArray & value, obj.attributes().value(key)) { list << QString::fromUtf8(value); } if (key == QLatin1String("kolabDescAttribute")) { QJson::Parser parser; foreach(const QString &attr, list) { bool ok; QMap map = parser.parse(attr.toUtf8(), &ok).toMap(); foreach(const QString &pKey, map.keys()) { QString value; if (map.value(pKey).type() == QVariant::Bool) { value = map.value(pKey).toBool() ? i18n("yes") : i18n("no"); } else { value = map.value(pKey).toString(); } mUi->formOwner->addRow(translateKolabLDAPAttributeForDisplay(pKey), new QLabel(value)); } } } else { mUi->formOwner->addRow(translateLDAPAttributeForDisplay(key), new QLabel(list.join("\n"))); } } } void ResourceManagement::slotDateChanged(QDate start, QDate end) { int days = start.daysTo(end); if (days < 7) { end = start.addDays(7); } mAgendaView->showDates(start, end); } #include "resourcemanagement.moc" \ No newline at end of file diff --git a/incidenceeditor-ng/resourcemanagement.h b/incidenceeditor-ng/resourcemanagement.h index 1943e5b954..3d5e0fc660 100644 --- a/incidenceeditor-ng/resourcemanagement.h +++ b/incidenceeditor-ng/resourcemanagement.h @@ -1,103 +1,103 @@ /* * Copyright 2014 Sandro Knauß * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #ifndef RESOURCEMANAGEMENT_H #define RESOURCEMANAGEMENT_H #include "incidenceeditors-ng_export.h" #include #include #include "freebusymodel/freebusycalendar.h" #include "resourceitem.h" -#include +#include #include #include #include #include class Ui_resourceManagement; namespace EventViews { class AgendaView; } namespace IncidenceEditorNG { class QTreeModel; class INCIDENCEEDITORS_NG_EXPORT ResourceManagement : public KDialog { Q_OBJECT public: ResourceManagement(); ~ResourceManagement(); ResourceItem::Ptr selectedItem() const; public slots: void slotDateChanged(QDate start, QDate end); private: /* Shows the details of a resource * */ void showDetails(const KLDAP::LdapObject&, const KLDAP::LdapClient &client); QItemSelectionModel *selectionModel; private slots: /* A new searchString is entered * */ void slotStartSearch(const QString&); /* A detail view is requested * */ void slotShowDetails(const QModelIndex & current); /** * The Owner search is done */ void slotOwnerSearchFinished(); void slotLayoutChanged(); private: FreeBusyItemModel *mModel; FreeBusyCalendar mFreebusyCalendar; ResourceItem::Ptr mOwnerItem; ResourceItem::Ptr mSelectedItem; EventViews::ViewCalendar::Ptr mFbCalendar; Ui_resourceManagement *mUi; QMap mFbEvent; EventViews::AgendaView *mAgendaView; }; } #endif // RESOURCEMANAGEMENT_H diff --git a/kontact/plugins/korganizer/apptsummarywidget.cpp b/kontact/plugins/korganizer/apptsummarywidget.cpp index 33fff041ac..b1f2ed7c6b 100644 --- a/kontact/plugins/korganizer/apptsummarywidget.cpp +++ b/kontact/plugins/korganizer/apptsummarywidget.cpp @@ -1,291 +1,304 @@ /* This file is part of Kontact. Copyright (c) 2003 Tobias Koenig Copyright (c) 2005-2006,2008-2009 Allen Winter This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #include "apptsummarywidget.h" #include "korganizerplugin.h" #include "summaryeventinfo.h" #include "korganizer/korganizerinterface.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include ApptSummaryWidget::ApptSummaryWidget( KOrganizerPlugin *plugin, QWidget *parent ) : KontactInterface::Summary( parent ), mPlugin( plugin ) { QVBoxLayout *mainLayout = new QVBoxLayout( this ); mainLayout->setSpacing( 3 ); mainLayout->setMargin( 3 ); QWidget *header = createHeader( this, QLatin1String("view-calendar-upcoming-events"), i18n( "Upcoming Events" ) ); mainLayout->addWidget( header ); mLayout = new QGridLayout(); mainLayout->addItem( mLayout ); mLayout->setSpacing( 3 ); mLayout->setRowStretch( 6, 1 ); QStringList mimeTypes; mimeTypes << KCalCore::Event::eventMimeType(); mCalendar = CalendarSupport::calendarSingleton(); mChanger = new Akonadi::IncidenceChanger( parent ); connect( mCalendar, SIGNAL(calendarChanged()), this, SLOT(updateView()) ); connect( mPlugin->core(), SIGNAL(dayChanged(QDate)), this, SLOT(updateView()) ); // Update Configuration configUpdated(); } ApptSummaryWidget::~ApptSummaryWidget() { } void ApptSummaryWidget::configUpdated() { KConfig config( QLatin1String("kcmapptsummaryrc") ); KConfigGroup group = config.group( "Days" ); mDaysAhead = group.readEntry( "DaysToShow", 7 ); group = config.group( "Show" ); mShowBirthdaysFromCal = group.readEntry( "BirthdaysFromCalendar", true ); mShowAnniversariesFromCal = group.readEntry( "AnniversariesFromCalendar", true ); group = config.group( "Groupware" ); mShowMineOnly = group.readEntry( "ShowMineOnly", false ); updateView(); } void ApptSummaryWidget::updateView() { qDeleteAll( mLabels ); mLabels.clear(); // The event print consists of the following fields: // icon:start date:days-to-go:summary:time range // where, // the icon is the typical event icon // the start date is the event start date // the days-to-go is the #days until the event starts // the summary is the event summary // the time range is the start-end time (only for non-floating events) QLabel *label = 0; int counter = 0; KIconLoader loader( QLatin1String("korganizer") ); QPixmap pm = loader.loadIcon( QLatin1String("view-calendar-day"), KIconLoader::Small ); QPixmap pmb = loader.loadIcon( QLatin1String("view-calendar-birthday"), KIconLoader::Small ); QPixmap pma = loader.loadIcon( QLatin1String("view-calendar-wedding-anniversary"), KIconLoader::Small ); QStringList uidList; SummaryEventInfo::setShowSpecialEvents( mShowBirthdaysFromCal, mShowAnniversariesFromCal ); QDate currentDate = QDate::currentDate(); SummaryEventInfo::List events = SummaryEventInfo::eventsForRange( currentDate, currentDate.addDays( mDaysAhead - 1 ), mCalendar ); foreach ( SummaryEventInfo *event, events ) { // Optionally, show only my Events /* if ( mShowMineOnly && !KCalCore::CalHelper::isMyCalendarIncidence( mCalendarAdaptor, event->ev ) ) { continue; } TODO: CalHelper is deprecated, remove this? */ KCalCore::Event::Ptr ev = event->ev; // print the first of the recurring event series only if ( ev->recurs() ) { if ( uidList.contains( ev->instanceIdentifier() ) ) { continue; } uidList.append( ev->instanceIdentifier() ); } // Icon label label = new QLabel( this ); if ( ev->categories().contains( QLatin1String("BIRTHDAY"), Qt::CaseInsensitive ) ) { label->setPixmap( pmb ); } else if ( ev->categories().contains( QLatin1String("ANNIVERSARY"), Qt::CaseInsensitive ) ) { label->setPixmap( pma ); } else { label->setPixmap( pm ); } label->setMaximumWidth( label->minimumSizeHint().width() ); mLayout->addWidget( label, counter, 0 ); mLabels.append( label ); // Start date or date span label QString dateToDisplay = event->startDate; if ( !event->dateSpan.isEmpty() ) { dateToDisplay = event->dateSpan; } label = new QLabel( dateToDisplay, this ); label->setAlignment( Qt::AlignLeft | Qt::AlignVCenter ); mLayout->addWidget( label, counter, 1 ); mLabels.append( label ); if ( event->makeBold ) { QFont font = label->font(); font.setBold( true ); label->setFont( font ); } // Days to go label label = new QLabel( event->daysToGo, this ); label->setAlignment( Qt::AlignLeft | Qt::AlignVCenter ); mLayout->addWidget( label, counter, 2 ); mLabels.append( label ); // Summary label KUrlLabel *urlLabel = new KUrlLabel( this ); urlLabel->setText( event->summaryText ); urlLabel->setUrl( event->summaryUrl ); urlLabel->installEventFilter( this ); urlLabel->setTextFormat( Qt::RichText ); urlLabel->setWordWrap( true ); mLayout->addWidget( urlLabel, counter, 3 ); mLabels.append( urlLabel ); connect( urlLabel, SIGNAL(leftClickedUrl(QString)), this, SLOT(viewEvent(QString)) ); connect( urlLabel, SIGNAL(rightClickedUrl(QString)), this, SLOT(popupMenu(QString)) ); if ( !event->summaryTooltip.isEmpty() ) { urlLabel->setToolTip( event->summaryTooltip ); } // Time range label (only for non-floating events) if ( !event->timeRange.isEmpty() ) { label = new QLabel( event->timeRange, this ); label->setAlignment( Qt::AlignLeft | Qt::AlignVCenter ); mLayout->addWidget( label, counter, 4 ); mLabels.append( label ); } counter++; } qDeleteAll( events ); if ( !counter ) { QLabel *noEvents = new QLabel( i18np( "No upcoming events starting within the next day", "No upcoming events starting within the next %1 days", mDaysAhead ), this ); noEvents->setAlignment( Qt::AlignHCenter | Qt::AlignVCenter ); mLayout->addWidget( noEvents, 0, 0 ); mLabels.append( noEvents ); } Q_FOREACH ( label, mLabels ) { //krazy:exclude=foreach as label is a pointer label->show(); } } +Akonadi::Item ApptSummaryWidget::findItem(const QString &uid) +{ + QStringList calendars = mCalendar->calendars(uid); + if (!calendars.isEmpty()) { + //FIXME don't just pick the first calendar + Akonadi::Item item = mCalendar->item( mCalendar->incidence(calendars.first(), uid) ); + return item; + } else { + kWarning() << "no calendar found"; + } + return Akonadi::Item(); +} + void ApptSummaryWidget::viewEvent( const QString &uid ) { - Akonadi::Item::Id id = mCalendar->item( uid ).id(); + Akonadi::Item::Id id = findItem( uid ).id(); if ( id != -1 ) { mPlugin->core()->selectPlugin( QLatin1String("kontact_korganizerplugin") ); //ensure loaded OrgKdeKorganizerKorganizerInterface korganizer( QLatin1String("org.kde.korganizer"), QLatin1String("/Korganizer"), QDBusConnection::sessionBus() ); korganizer.editIncidence( QString::number( id ) ); } } void ApptSummaryWidget::removeEvent( const Akonadi::Item &item ) { mChanger->deleteIncidence( item ); } void ApptSummaryWidget::popupMenu( const QString &uid ) { KMenu popup( this ); // FIXME: Should say "Show Appointment" if we don't have rights to edit // Doesn't make sense to edit events from birthday resource for example QAction *editIt = popup.addAction( i18n( "&Edit Appointment..." ) ); QAction *delIt = popup.addAction( i18n( "&Delete Appointment" ) ); delIt->setIcon( KIconLoader::global()-> loadIcon( QLatin1String("edit-delete"), KIconLoader::Small ) ); - Akonadi::Item item = mCalendar->item( uid ); + Akonadi::Item item = findItem(uid); delIt->setEnabled( mCalendar->hasRight( item, Akonadi::Collection::CanDeleteItem ) ); const QAction *selectedAction = popup.exec( QCursor::pos() ); if ( selectedAction == editIt ) { - viewEvent( uid ); + viewEvent( uid ); } else if ( selectedAction == delIt ) { - removeEvent( item ); + removeEvent( item ); } } bool ApptSummaryWidget::eventFilter( QObject *obj, QEvent *e ) { if ( obj->inherits( "KUrlLabel" ) ) { KUrlLabel *label = static_cast( obj ); if ( e->type() == QEvent::Enter ) { emit message( i18n( "Edit Event: \"%1\"", label->text() ) ); } if ( e->type() == QEvent::Leave ) { emit message( QString::null ); //krazy:exclude=nullstrassign for old broken gcc } } return KontactInterface::Summary::eventFilter( obj, e ); } QStringList ApptSummaryWidget::configModules() const { return QStringList()<< QLatin1String("kcmapptsummary.desktop"); } diff --git a/kontact/plugins/korganizer/apptsummarywidget.h b/kontact/plugins/korganizer/apptsummarywidget.h index 95bfe3bb06..4c5c144601 100644 --- a/kontact/plugins/korganizer/apptsummarywidget.h +++ b/kontact/plugins/korganizer/apptsummarywidget.h @@ -1,85 +1,86 @@ /* This file is part of Kontact. Copyright (c) 2003 Tobias Koenig Copyright (c) 2005-2006,2008-2009 Allen Winter This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #ifndef SUMMARYWIDGET_H #define SUMMARYWIDGET_H #include class KOrganizerPlugin; namespace Akonadi { class Item; class IncidenceChanger; class ETMCalendar; } class QDate; class QGridLayout; class QLabel; class ApptSummaryWidget : public KontactInterface::Summary { Q_OBJECT public: ApptSummaryWidget( KOrganizerPlugin *plugin, QWidget *parent ); ~ApptSummaryWidget(); int summaryHeight() const { return 3; } QStringList configModules() const; void configUpdated(); void updateSummary( bool force = false ) { Q_UNUSED( force ); updateView(); } protected: virtual bool eventFilter( QObject *obj, QEvent *e ); private slots: void updateView(); void popupMenu( const QString &uid ); void viewEvent( const QString &uid ); void removeEvent( const Akonadi::Item &item ); private: + Akonadi::Item findItem(const QString &uid); void dateDiff( const QDate &date, int &days ); Akonadi::ETMCalendar *mCalendar; Akonadi::IncidenceChanger *mChanger; QGridLayout *mLayout; QList mLabels; KOrganizerPlugin *mPlugin; int mDaysAhead; bool mShowBirthdaysFromCal; bool mShowAnniversariesFromCal; bool mShowMineOnly; }; #endif diff --git a/korgac/alarmdialog.cpp b/korgac/alarmdialog.cpp index 6bbd867c98..4eef816343 100644 --- a/korgac/alarmdialog.cpp +++ b/korgac/alarmdialog.cpp @@ -1,1038 +1,1045 @@ /* This file is part of the KDE reminder agent. Copyright (c) 2000,2003 Cornelius Schumacher Copyright (c) 2008-2009 Allen Winter Copyright (c) 2009-2010 Klarälvdalens Datakonsult AB, a KDAB Group company This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #include "alarmdialog.h" #include "korganizer_interface.h" #include "mailclient.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace KPIMIdentities; using namespace KCalCore; using namespace KCalUtils; static int defSuspendVal = 5; static int defSuspendUnit = 0; // 0=>minutes, 1=>hours, 2=>days, 3=>weeks class ReminderTree : public QTreeWidget { public: ReminderTree( QWidget *parent ) : QTreeWidget( parent ) { } /*reimp*/ void mouseReleaseEvent( QMouseEvent *event ); }; void ReminderTree::mouseReleaseEvent( QMouseEvent *event ) { QTreeWidgetItem *item = itemAt( event->pos() ); if ( item ) { if ( event->button() == Qt::LeftButton ) { emit itemActivated( item, 0 ); } else if ( event->button() == Qt::RightButton ) { emit customContextMenuRequested( event->pos() ); } } } class ReminderTreeItem : public QTreeWidgetItem { public: ReminderTreeItem( const Akonadi::Item &incidence, QTreeWidget *parent ) : QTreeWidgetItem( parent ), mIncidence( incidence ), mNotified( false ) { } bool operator<( const QTreeWidgetItem & other ) const; QString mDisplayText; const Akonadi::Item mIncidence; QDateTime mRemindAt; KDateTime mTrigger; KDateTime mHappening; bool mNotified; }; struct ConfItem { QString uid; KUrl akonadiUrl; QDateTime remindAt; }; bool ReminderTreeItem::operator<( const QTreeWidgetItem &other ) const { switch( treeWidget()->sortColumn() ) { case 1: // happening datetime { const ReminderTreeItem *item = static_cast( &other ); return item->mHappening.secsTo( mHappening ); } case 2: // trigger datetime { const ReminderTreeItem *item = static_cast( &other ); return item->mTrigger.secsTo( mTrigger ); } default: return QTreeWidgetItem::operator < ( other ); } } typedef QList ReminderList; AlarmDialog::AlarmDialog( const Akonadi::ETMCalendar::Ptr &calendar, QWidget *parent ) : KDialog( parent, Qt::WindowStaysOnTopHint ), mCalendar( calendar ), mSuspendTimer( this ) { // User1 => Edit... // User2 => Dismiss All // User3 => Dismiss Selected // Ok => Suspend connect( calendar.data(), SIGNAL(calendarChanged()), SLOT(slotCalendarChanged()) ); KIconLoader::global()->addAppDir( QLatin1String("korgac") ); KSharedConfig::Ptr config = KGlobal::config(); KConfigGroup generalConfig( config, "General" ); QPoint pos = generalConfig.readEntry( "Position", QPoint( 0, 0 ) ); QWidget *topBox = new QWidget( this ); if ( !pos.isNull() ) { mPos = pos; topBox->move( mPos ); } setMainWidget( topBox ); setCaption( i18nc( "@title:window", "Reminders" ) ); setWindowIcon( KIcon( QLatin1String("korgac") ) ); setButtons( Ok | User1 | User2 | User3 ); setDefaultButton( NoDefault ); setButtonText( User3, i18nc( "@action:button", "Dismiss Reminder" ) ); setButtonToolTip( User3, i18nc( "@info:tooltip", "Dismiss the reminders for the selected incidences" ) ); setButtonText( User2, i18nc( "@action:button", "Dismiss All" ) ); setButtonToolTip( User2, i18nc( "@info:tooltip", "Dismiss the reminders for all listed incidences" ) ); setButtonText( User1, i18nc( "@action:button", "Edit..." ) ); setButtonToolTip( User1, i18nc( "@info:tooltip", "Edit the selected incidence" ) ); setButtonText( Ok, i18nc( "@action:button", "Suspend" ) ); setButtonToolTip( Ok, i18nc( "@info:tooltip", "Suspend the reminders for the selected incidences " "by the specified interval" ) ); // Try to keep the dialog small and non-obtrusive. setMinimumWidth( 575 ); setMinimumHeight( 300 ); QVBoxLayout *mTopLayout = new QVBoxLayout( topBox ); QLabel *label = new QLabel( i18nc( "@label", "Reminders: " "Click on a title to toggle the details viewer for that item" ), topBox ); mTopLayout->addWidget( label ); mIncidenceTree = new ReminderTree( topBox ); mIncidenceTree->setColumnCount( 3 ); mIncidenceTree->setSortingEnabled( true ); const QStringList headerLabels = ( QStringList( i18nc( "@title:column reminder title", "Title" ) ) << i18nc( "@title:column happens at date/time", "Date Time" ) << i18nc( "@title:column trigger date/time", "Trigger Time" ) ); mIncidenceTree->setHeaderLabels( headerLabels ); mIncidenceTree->headerItem()->setToolTip( 0, i18nc( "@info:tooltip", "The event or to-do title" ) ); mIncidenceTree->headerItem()->setToolTip( 1, i18nc( "@info:tooltip", "The reminder is set for this date/time" ) ); mIncidenceTree->headerItem()->setToolTip( 2, i18nc( "@info:tooltip", "The date/time the reminder was triggered" ) ); mIncidenceTree->setWordWrap( true ); mIncidenceTree->setAllColumnsShowFocus( true ); mIncidenceTree->setSelectionMode( QAbstractItemView::ExtendedSelection ); mIncidenceTree->setRootIsDecorated( false ); mTopLayout->addWidget( mIncidenceTree ); connect( mIncidenceTree, SIGNAL(itemActivated(QTreeWidgetItem*,int)), SLOT(update()) ); connect( mIncidenceTree, SIGNAL(itemDoubleClicked(QTreeWidgetItem*,int)), SLOT(edit()) ); connect( mIncidenceTree, SIGNAL(customContextMenuRequested(QPoint)), SLOT(popupItemMenu(QPoint)) ); mDetailView = new CalendarSupport::IncidenceViewer( mCalendar.data(), topBox ); QString s; s = i18nc( "@info default incidence details string", "Select an event or to-do from the list above " "to view its details here." ); mDetailView->setDefaultMessage( s ); mTopLayout->addWidget( mDetailView ); mDetailView->hide(); mLastItem = 0; KHBox *suspendBox = new KHBox( topBox ); suspendBox->setSpacing( spacingHint() ); mTopLayout->addWidget( suspendBox ); QLabel *l = new QLabel( i18nc( "@label:spinbox", "Suspend &duration:" ), suspendBox ); mSuspendSpin = new QSpinBox( suspendBox ); mSuspendSpin->setRange( 1, 9999 ); mSuspendSpin->setValue( defSuspendVal ); // default suspend duration mSuspendSpin->setToolTip( i18nc( "@info:tooltip", "Suspend the reminders by this amount of time" ) ); mSuspendSpin->setWhatsThis( i18nc( "@info:whatsthis", "Each reminder for the selected incidences will be suspended " "by this number of time units. You can choose the time units " "(typically minutes) in the adjacent selector." ) ); l->setBuddy( mSuspendSpin ); mSuspendUnit = new KComboBox( suspendBox ); mSuspendUnit->addItem( i18nc( "@item:inlistbox suspend in terms of minutes", "minute(s)" ) ); mSuspendUnit->addItem( i18nc( "@item:inlistbox suspend in terms of hours", "hour(s)" ) ); mSuspendUnit->addItem( i18nc( "@item:inlistbox suspend in terms of days", "day(s)" ) ); mSuspendUnit->addItem( i18nc( "@item:inlistbox suspend in terms of weeks", "week(s)" ) ); mSuspendUnit->setToolTip( i18nc( "@info:tooltip", "Suspend the reminders using this time unit" ) ); mSuspendUnit->setWhatsThis( i18nc( "@info:whatsthis", "Each reminder for the selected incidences will be suspended " "using this time unit. You can set the number of time units " "in the adjacent number entry input." ) ); mSuspendUnit->setCurrentIndex( defSuspendUnit ); connect( &mSuspendTimer, SIGNAL(timeout()), SLOT(wakeUp()) ); connect( this, SIGNAL(okClicked()), this, SLOT(slotOk()) ); connect( this, SIGNAL(user1Clicked()), this, SLOT(slotUser1()) ); connect( this, SIGNAL(user2Clicked()), this, SLOT(slotUser2()) ); connect( this, SIGNAL(user3Clicked()), this, SLOT(slotUser3()) ); mIdentityManager = new CalendarSupport::IdentityManager; } AlarmDialog::~AlarmDialog() { mIncidenceTree->clear(); delete mIdentityManager; } ReminderTreeItem *AlarmDialog::searchByItem( const Akonadi::Item &incidence ) { ReminderTreeItem *found = 0; QTreeWidgetItemIterator it( mIncidenceTree ); while ( *it ) { ReminderTreeItem *item = static_cast( *it ); if ( item->mIncidence == incidence ) { found = item; break; } ++it; } return found; } static QString cleanSummary( const QString &summary ) { static QString etc = i18nc( "@label an elipsis", "..." ); int maxLen = 30; QString retStr = summary; retStr.replace( QLatin1Char('\n'), QLatin1Char(' ') ); if ( retStr.length() > maxLen ) { maxLen -= etc.length(); retStr = retStr.left( maxLen ); retStr += etc; } return retStr; } void AlarmDialog::addIncidence( const Akonadi::Item &incidenceitem, const QDateTime &reminderAt, const QString &displayText ) { Incidence::Ptr incidence = CalendarSupport::incidence( incidenceitem ); ReminderTreeItem *item = searchByItem( incidenceitem ); if ( !item ) { item = new ReminderTreeItem( incidenceitem, mIncidenceTree ); } item->mNotified = false; item->mHappening = KDateTime(); item->mRemindAt = reminderAt; item->mTrigger = KDateTime::currentLocalDateTime(); item->mDisplayText = displayText; item->setText( 0, cleanSummary( incidence->summary() ) ); Event::Ptr event; Todo::Ptr todo; QString displayStr; const KDateTime dateTime = triggerDateForIncidence( incidence, reminderAt, displayStr ); if ( incidence->type() == Incidence::TypeEvent ) { item->setIcon( 0, SmallIcon( QLatin1String("view-calendar-day") ) ); } else if ( incidence->type() == Incidence::TypeTodo ) { item->setIcon( 0, SmallIcon( QLatin1String("view-calendar-tasks")) ); } item->mHappening = dateTime; item->setText( 1, displayStr ); item->setText( 2, IncidenceFormatter::dateTimeToString( item->mTrigger, false, true, KDateTime::Spec::LocalZone() ) ); QString tip = IncidenceFormatter::toolTipStr( CalendarSupport::displayName( mCalendar.data(), incidenceitem.parentCollection() ), incidence, item->mRemindAt.date(), true, KDateTime::Spec::LocalZone() ); if ( !item->mDisplayText.isEmpty() ) { tip += QLatin1String("
    ") + item->mDisplayText; } item->setToolTip( 0, tip ); item->setToolTip( 1, tip ); item->setToolTip( 2, tip ); item->setData( 0, QTreeWidgetItem::UserType, false ); mIncidenceTree->setCurrentItem( item ); showDetails( item ); slotSave(); } void AlarmDialog::slotOk() { suspend(); } void AlarmDialog::slotUser1() { edit(); } void AlarmDialog::slotUser2() { dismissAll(); } void AlarmDialog::slotUser3() { dismissCurrent(); } void AlarmDialog::dismissCurrent() { dismiss( selectedItems() ); if ( activeCount() == 0 ) { accept(); } else { update(); } emit reminderCount( activeCount() ); } void AlarmDialog::dismissAll() { ReminderList selections; QTreeWidgetItemIterator it( mIncidenceTree ); while ( *it ) { if ( !(*it)->isDisabled() ) { //do not disable suspended reminders selections.append( static_cast( *it ) ); } ++it; } dismiss( selections ); setTimer(); accept(); emit reminderCount( activeCount() ); } void AlarmDialog::dismiss( ReminderList selections ) { QList ids; for ( ReminderList::Iterator it = selections.begin(); it != selections.end(); ++it ) { kDebug() << "removing " << CalendarSupport::incidence( (*it)->mIncidence )->summary(); if ( mIncidenceTree->itemBelow( *it ) ) { mIncidenceTree->setCurrentItem( mIncidenceTree->itemBelow( *it ) ); } else if ( mIncidenceTree->itemAbove( *it ) ) { mIncidenceTree->setCurrentItem( mIncidenceTree->itemAbove( *it ) ); } mIncidenceTree->removeItemWidget( *it, 0 ); ids.append( (*it)->mIncidence.id() ); delete *it; } removeFromConfig( ids ); } void AlarmDialog::edit() { ReminderList selection = selectedItems(); if ( selection.count() != 1 ) { return; } Incidence::Ptr incidence = CalendarSupport::incidence( selection.first()->mIncidence ); if ( !mCalendar->hasRight( selection.first()->mIncidence, Akonadi::Collection::CanChangeItem ) ) { KMessageBox::sorry( this, i18nc( "@info", "\"%1\" is a read-only item so modifications are not possible.", cleanSummary( incidence->summary() ) ) ); return; } #if !defined(KDEPIM_MOBILE_UI) openIncidenceEditorNG( selection.first()->mIncidence ); #else openIncidenceEditorThroughKOrganizer( incidence ); #endif } void AlarmDialog::suspend() { if ( !isVisible() ) { //do nothing if the dialog is hidden return; } int unit = 1; switch ( mSuspendUnit->currentIndex() ) { case 3: // weeks unit *= 7; case 2: // days unit *= 24; case 1: // hours unit *= 60; case 0: // minutes unit *= 60; default: break; } ReminderTreeItem *selitem = 0; QTreeWidgetItemIterator it( mIncidenceTree ); while ( *it ) { if ( (*it)->isSelected() && !(*it)->isDisabled() ) { //suspend selected, non-suspended reminders (*it)->setSelected( false ); (*it)->setDisabled( true ); ReminderTreeItem *item = static_cast( *it ); item->mRemindAt = QDateTime::currentDateTime().addSecs( unit * mSuspendSpin->value() ); item->mHappening = KDateTime( item->mRemindAt, KDateTime::Spec::LocalZone() ); item->mNotified = false; (*it)->setText( 1, KGlobal::locale()->formatDateTime( item->mHappening ) ); selitem = item; } ++it; } if ( selitem ) { if ( mIncidenceTree->itemBelow( selitem ) ) { mIncidenceTree->setCurrentItem( mIncidenceTree->itemBelow( selitem ) ); } else if ( mIncidenceTree->itemAbove( selitem ) ) { mIncidenceTree->setCurrentItem( mIncidenceTree->itemAbove( selitem ) ); } } // save suspended alarms too so they can be restored on restart // kolab/issue4108 slotSave(); setTimer(); if ( activeCount() == 0 ) { accept(); } else { update(); } emit reminderCount( activeCount() ); } void AlarmDialog::setTimer() { int nextReminderAt = -1; QTreeWidgetItemIterator it( mIncidenceTree ); while ( *it ) { ReminderTreeItem *item = static_cast( *it ); if ( item->mRemindAt > QDateTime::currentDateTime() ) { const int secs = QDateTime::currentDateTime().secsTo( item->mRemindAt ); nextReminderAt = nextReminderAt <= 0 ? secs : qMin( nextReminderAt, secs ); } ++it; } if ( nextReminderAt >= 0 ) { mSuspendTimer.stop(); mSuspendTimer.start( 1000 * ( nextReminderAt + 1 ) ); mSuspendTimer.setSingleShot( true ); } } void AlarmDialog::show() { mIncidenceTree->resizeColumnToContents( 0 ); mIncidenceTree->resizeColumnToContents( 1 ); mIncidenceTree->resizeColumnToContents( 2 ); mIncidenceTree->sortItems( 1, Qt::AscendingOrder ); // select the first item that hasn't already been notified QTreeWidgetItemIterator it( mIncidenceTree ); while ( *it ) { ReminderTreeItem *item = static_cast( *it ); if ( !item->mNotified ) { (*it)->setSelected( true ); break; } ++it; } // reset the default suspend time // Allen: commented-out the following lines on 17 Sept 2013 // mSuspendSpin->setValue( defSuspendVal ); // mSuspendUnit->setCurrentIndex( defSuspendUnit ); KDialog::show(); if ( !mPos.isNull() ) { KDialog::move( mPos ); } KWindowSystem::unminimizeWindow( winId(), false ); KWindowSystem::setState( winId(), NET::KeepAbove | NET::DemandsAttention ); KWindowSystem::setOnAllDesktops( winId(), true ); KWindowSystem::activateWindow( winId() ); // Audio, Procedure, and EMail alarms eventNotification(); } void AlarmDialog::suspendAll() { mIncidenceTree->clearSelection(); QTreeWidgetItemIterator it( mIncidenceTree ); // first, select all non-suspended reminders while ( *it ) { if ( !(*it)->isDisabled() ) { //do not suspend suspended reminders (*it)->setSelected( true ); } ++it; } //suspend all selected reminders suspend(); } void AlarmDialog::eventNotification() { bool beeped = false; bool found = false; ReminderList list; QTreeWidgetItemIterator it( mIncidenceTree ); while ( *it ) { ReminderTreeItem *item = static_cast( *it ); ++it; if ( item->isDisabled() || item->mNotified ) { //skip suspended reminders or reminders that have been notified continue; } found = true; item->mNotified = true; Incidence::Ptr incidence = CalendarSupport::incidence( item->mIncidence ); Alarm::List alarms = incidence->alarms(); Alarm::List::ConstIterator ait; for ( ait = alarms.constBegin(); ait != alarms.constEnd(); ++ait ) { Alarm::Ptr alarm = *ait; // FIXME: Check whether this should be done for all multiple alarms if ( alarm->type() == Alarm::Procedure ) { // FIXME: Add a message box asking whether the procedure should really be executed kDebug() << "Starting program: '" << alarm->programFile() << "'"; QString program = alarm->programFile(); // if the program name contains spaces escape it if ( program.contains( QLatin1Char(' ') ) && !( program.startsWith( QLatin1Char('\"') ) && program.endsWith( QLatin1Char('\"') ) ) ) { program = QLatin1Char('\"') + program + QLatin1Char('\"'); } QProcess::startDetached( program + QLatin1Char(' ') + alarm->programArguments() ); } else if ( alarm->type() == Alarm::Audio ) { beeped = true; Phonon::MediaObject *player = Phonon::createPlayer( Phonon::NotificationCategory, KUrl( alarm->audioFile() ) ); player->setParent( this ); connect( player, SIGNAL(finished()), player, SLOT(deleteLater()) ); player->play(); } else if ( alarm->type() == Alarm::Email ) { QString from = CalendarSupport::KCalPrefs::instance()->email(); Identity id = mIdentityManager->identityForAddress( from ); QString to; if ( alarm->mailAddresses().isEmpty() ) { to = from; } else { const Person::List addresses = alarm->mailAddresses(); QStringList add; for ( Person::List::ConstIterator it = addresses.constBegin(); it != addresses.constEnd(); ++it ) { add << (*it)->fullName(); } to = add.join( QLatin1String(", ") ); } QString subject; - Akonadi::Item parentItem = mCalendar->item( alarm->parentUid() ); - Incidence::Ptr parent = CalendarSupport::incidence( parentItem ); + Incidence::Ptr parent; + QStringList calendars = mCalendar->calendars(alarm->parentUid()); + if (!calendars.isEmpty()) { + //FIXME don't just pick the first calendar + parent = mCalendar->incidence(calendars.first(), alarm->parentUid()); + } else { + kWarning() << "Couldn't find incidence " << alarm->parentUid(); + return; + } if ( alarm->mailSubject().isEmpty() ) { if ( parent->summary().isEmpty() ) { subject = i18nc( "@title", "Reminder" ); } else { subject = i18nc( "@title", "Reminder: %1", cleanSummary( parent->summary() ) ); } } else { subject = i18nc( "@title", "Reminder: %1", alarm->mailSubject() ); } QString body = IncidenceFormatter::mailBodyStr( parent.staticCast(), KSystemTimeZones::local() ); if ( !alarm->mailText().isEmpty() ) { body += QLatin1Char('\n') + alarm->mailText(); } //TODO: support attachments KOrg::MailClient mailer; mailer.send( id, from, to, QString(), subject, body, true, false, QString(), MailTransport::TransportManager::self()->defaultTransportName() ); } } } if ( !beeped && found ) { KNotification::beep(); } } void AlarmDialog::wakeUp() { bool activeReminders = false; QTreeWidgetItemIterator it( mIncidenceTree ); QTreeWidgetItem *firstItem = 0; while ( *it ) { if ( !firstItem ) { firstItem = *it; } ReminderTreeItem *item = static_cast( *it ); Incidence::Ptr incidence = CalendarSupport::incidence( item->mIncidence ); if ( item->mRemindAt <= QDateTime::currentDateTime() ) { if ( item->isDisabled() ) { //do not wakeup non-suspended reminders item->setDisabled( false ); item->setSelected( false ); } activeReminders = true; } else { item->setDisabled( true ); } ++it; } if ( activeReminders ) { show(); } setTimer(); showDetails( firstItem ); emit reminderCount( activeCount() ); } void AlarmDialog::slotSave() { KSharedConfig::Ptr config = KGlobal::config(); KConfigGroup generalConfig( config, "General" ); int numReminders = 0; QTreeWidgetItemIterator it( mIncidenceTree ); while ( *it ) { ReminderTreeItem *item = static_cast( *it ); KConfigGroup incidenceConfig( config, QString::fromLatin1( "Incidence-%1" ).arg( numReminders + 1 ) ); Incidence::Ptr incidence = CalendarSupport::incidence( item->mIncidence ); incidenceConfig.writeEntry( "AkonadiUrl", item->mIncidence.url() ); incidenceConfig.writeEntry( "RemindAt", item->mRemindAt ); ++numReminders; ++it; } generalConfig.writeEntry( "Reminders", numReminders ); generalConfig.writeEntry( "Position", pos() ); config->sync(); } ReminderList AlarmDialog::selectedItems() const { ReminderList list; QTreeWidgetItemIterator it( mIncidenceTree ); while ( *it ) { if ( (*it)->isSelected() ) { list.append( static_cast( *it ) ); } ++it; } return list; } int AlarmDialog::activeCount() { int count = 0; QTreeWidgetItemIterator it( mIncidenceTree ); while ( *it ) { if ( !(*it)->isDisabled() ) { //suspended reminders are non-active ++count; } ++it; } kDebug() << "computed " << count << " active reminders"; return count; } void AlarmDialog::closeEvent( QCloseEvent * ) { slotSave(); accept(); } void AlarmDialog::updateButtons() { const int count = selectedItems().count(); kDebug() << "selected items=" << count; enableButton( User3, count > 0 ); // enable Dismiss, if >1 selected enableButton( User1, count == 1 ); // enable Edit, if only 1 selected enableButton( Ok, count > 0 ); // enable Suspend, if >1 selected } void AlarmDialog::toggleDetails( QTreeWidgetItem *item ) { if ( !item ) { return; } if ( !mDetailView->isHidden() ) { if ( mLastItem == item ) { resize( size().width(), size().height() - mDetailView->height() - 50 ); mDetailView->hide(); } else { showDetails( item ); } } else { resize( size().width(), size().height() + mDetailView->height() + 50 ); showDetails( item ); mDetailView->show(); } mLastItem = item; } void AlarmDialog::showDetails( QTreeWidgetItem *item ) { if ( !item ) { return; } ReminderTreeItem *reminderItem = dynamic_cast( item ); if ( !reminderItem ) { mDetailView->setIncidence( Akonadi::Item() ); } else { if ( !reminderItem->mDisplayText.isEmpty() ) { QString txt = QLatin1String("

    ") + reminderItem->mDisplayText + QLatin1String("

    "); mDetailView->setHeaderText( txt ); } else { mDetailView->setHeaderText( QString() ); } Incidence::Ptr incidence = CalendarSupport::incidence( reminderItem->mIncidence ); mDetailView->setIncidence( reminderItem->mIncidence, reminderItem->mRemindAt.date() ); } } void AlarmDialog::update() { updateButtons(); if ( !mIncidenceTree->selectedItems().isEmpty() ) { QTreeWidgetItem *item = mIncidenceTree->selectedItems().first(); toggleDetails( item ); } } void AlarmDialog::popupItemMenu( const QPoint &point ) { QTreeWidgetItem *item = mIncidenceTree->itemAt( point ); if ( !item ) { return; } ReminderTreeItem *reminderItem = dynamic_cast( item ); if ( reminderItem ) { Incidence::Ptr incidence = CalendarSupport::incidence( reminderItem->mIncidence ); } } void AlarmDialog::accept() { if ( activeCount() == 0 ) { mPos = pos(); hide(); } } /** static */ KDateTime AlarmDialog::triggerDateForIncidence( const Incidence::Ptr &incidence, const QDateTime &reminderAt, QString &displayStr ) { KDateTime result; if ( incidence->alarms().isEmpty() ) { return result; } Alarm::Ptr alarm = incidence->alarms().first(); if ( incidence->recurs() ) { result = incidence->recurrence()->getNextDateTime( KDateTime( reminderAt, KDateTime::Spec::LocalZone( ) ) ); displayStr = KGlobal::locale()->formatDateTime( result.toLocalZone() ); } if ( !result.isValid() ) { result = incidence->dateTime( Incidence::RoleAlarm ); displayStr = IncidenceFormatter::dateTimeToString( result, false, true, KDateTime::Spec::LocalZone() ); } return result; } void AlarmDialog::slotCalendarChanged() { KCalCore::Incidence::List incidences = mCalendar->incidences(); Akonadi::Item::List items = mCalendar->itemList( incidences ); for ( Akonadi::Item::List::ConstIterator it = items.constBegin(); it != items.constEnd(); ++it ) { ReminderTreeItem *item = searchByItem( *it ); if ( item ) { Incidence::Ptr incidence = CalendarSupport::incidence( *it ); QString displayStr; // Yes, alarms can be empty, if someone edited the incidence and removed all alarms if ( !incidence->alarms().isEmpty() ) { const KDateTime dateTime = triggerDateForIncidence( incidence, item->mRemindAt, displayStr ); const QString summary = cleanSummary( incidence->summary() ); if ( displayStr != item->text( 1 ) || summary != item->text( 0 ) ) { item->setText( 1, displayStr ); item->setText( 0, summary ); } } } } } void AlarmDialog::keyPressEvent( QKeyEvent *e ) { const int key = e->key() | e->modifiers(); if ( key == Qt::Key_Enter || key == Qt::Key_Return ) { e->ignore(); return; } KDialog::keyPressEvent( e ); } bool AlarmDialog::openIncidenceEditorThroughKOrganizer( const Incidence::Ptr &incidence ) { if ( !QDBusConnection::sessionBus().interface()->isServiceRegistered( QLatin1String("org.kde.korganizer") ) ) { if ( KToolInvocation::startServiceByDesktopName( QLatin1String("korganizer"), QString() ) ) { KMessageBox::error( this, i18nc( "@info", "Could not start KOrganizer so editing is not possible." ) ); return false; } } org::kde::korganizer::Korganizer korganizer( QLatin1String("org.kde.korganizer"), QLatin1String("/Korganizer"), QDBusConnection::sessionBus() ); kDebug() << "editing incidence " << incidence->summary(); if ( !korganizer.editIncidence( incidence->uid() ) ) { KMessageBox::error( this, i18nc( "@info", "An internal KOrganizer error occurred attempting to modify \"%1\"", cleanSummary( incidence->summary() ) ) ); } // get desktop # where korganizer (or kontact) runs QString object = QDBusConnection::sessionBus().interface()->isServiceRegistered( QLatin1String("org.kde.kontact") ) ? QLatin1String("kontact/MainWindow_1") : QLatin1String("korganizer/MainWindow_1"); QDBusInterface korganizerObj( QLatin1String("org.kde.korganizer"), QLatin1Char('/') + object ); #ifdef Q_WS_X11 QDBusReply reply = korganizerObj.call( QLatin1String("winId") ); if ( reply.isValid() ) { int window = reply; int desktop = KWindowSystem::windowInfo( window, NET::WMDesktop ).desktop(); if ( KWindowSystem::currentDesktop() == desktop ) { KWindowSystem::minimizeWindow( winId(), false ); } else { KWindowSystem::setCurrentDesktop( desktop ); } KWindowSystem::activateWindow( KWindowSystem::transientFor( window ) ); } #elif defined(Q_WS_WIN) // WId is a typedef to a void* on windows QDBusReply reply = korganizerObj.call( QLatin1String("winId") ); if ( reply.isValid() ) { qlonglong window = reply; KWindowSystem::minimizeWindow( winId(), false ); KWindowSystem::allowExternalProcessWindowActivation(); KWindowSystem::activateWindow( reinterpret_cast(window) ); } #else // TODO (mac) #endif return true; } bool AlarmDialog::openIncidenceEditorNG( const Akonadi::Item &item ) { Incidence::Ptr incidence = CalendarSupport::incidence( item ); IncidenceEditorNG::IncidenceDialog *dialog = IncidenceEditorNG::IncidenceDialogFactory::create( false, /*doesn't need initial saving*/ incidence->type(), 0, this ); dialog->load( item ); return true; } void AlarmDialog::removeFromConfig( const QList &ids ) { KSharedConfig::Ptr config = KGlobal::config(); KConfigGroup genGroup( config, "General" ); const int oldNumReminders = genGroup.readEntry( "Reminders", 0 ); QList newReminders; // Delete everything for ( int i = 1; i <= oldNumReminders; ++i ) { const QString group( QString::fromLatin1( "Incidence-%1" ).arg( i ) ); KConfigGroup incGroup( config, group ); const QString uid = incGroup.readEntry( "UID" ); const QDateTime remindAtDate = incGroup.readEntry( "RemindAt", QDateTime() ); const KUrl akonadiUrl = incGroup.readEntry( "AkonadiUrl" ); const Akonadi::Item::Id id = Akonadi::Item::fromUrl( akonadiUrl ).id(); if ( !ids.contains( id ) ) { ConfItem ci; ci.akonadiUrl = akonadiUrl; ci.remindAt = remindAtDate; ci.uid = uid; newReminders.append( ci ); } config->deleteGroup( group ); } genGroup.writeEntry( "Reminders", newReminders.count() ); //Write everything except those which have an uid we don't want for ( int i = 0; i < newReminders.count(); ++i ) { const QString group( QString::fromLatin1( "Incidence-%1" ).arg( i + 1 ) ); KConfigGroup incGroup( config, group ); incGroup.writeEntry( "UID", newReminders[i].uid ); incGroup.writeEntry( "RemindAt", newReminders[i].remindAt ); incGroup.writeEntry( "AkonadiUrl", newReminders[i].akonadiUrl ); incGroup.sync(); } genGroup.sync(); } diff --git a/korgac/koalarmclient.cpp b/korgac/koalarmclient.cpp index 22f27467d0..81e0d61eb6 100644 --- a/korgac/koalarmclient.cpp +++ b/korgac/koalarmclient.cpp @@ -1,361 +1,372 @@ /* This file is part of the KDE reminder agent. Copyright (c) 2002,2003 Cornelius Schumacher This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ //krazy:excludeall=kdebug because we use the korgac(check) debug area in here #include "koalarmclient.h" #if !defined(KORGAC_AKONADI_AGENT) #include "alarmdialog.h" #include "alarmdockwindow.h" #else #include #endif #include "korgacadaptor.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef Q_WS_MAEMO_5 #include #endif using namespace KCalCore; KOAlarmClient::KOAlarmClient( QObject *parent ) : QObject( parent ), mDocker( 0 ), mDialog( 0 ) { new KOrgacAdaptor( this ); Akonadi::DBusConnectionPool::threadConnection().registerObject( QLatin1String("/ac"), this ); kDebug(); #if !defined(KORGAC_AKONADI_AGENT) if ( dockerEnabled() ) { mDocker = new AlarmDockWindow; connect( this, SIGNAL(reminderCount(int)), mDocker, SLOT(slotUpdate(int)) ); connect( mDocker, SIGNAL(quitSignal()), SLOT(slotQuit()) ); } #endif QStringList mimeTypes; mimeTypes << Event::eventMimeType() << Todo::todoMimeType(); mCalendar = Akonadi::ETMCalendar::Ptr( new Akonadi::ETMCalendar( mimeTypes ) ); mCalendar->setObjectName( QLatin1String("KOrgac's calendar") ); mETM = mCalendar->entityTreeModel(); connect( &mCheckTimer, SIGNAL(timeout()), SLOT(checkAlarms()) ); connect( mETM, SIGNAL(collectionPopulated(Akonadi::Collection::Id)), SLOT(deferredInit()) ); connect( mETM, SIGNAL(collectionTreeFetched(Akonadi::Collection::List)), SLOT(deferredInit()) ); KConfigGroup alarmGroup( KGlobal::config(), "Alarms" ); const int interval = alarmGroup.readEntry( "Interval", 60 ); kDebug() << "KOAlarmClient check interval:" << interval << "seconds."; mLastChecked = alarmGroup.readEntry( "CalendarsLastChecked", QDateTime() ); checkAlarms(); mCheckTimer.start( 1000 * interval ); // interval in seconds } KOAlarmClient::~KOAlarmClient() { #if !defined(KORGAC_AKONADI_AGENT) delete mDocker; delete mDialog; #endif } void checkAllItems( KCheckableProxyModel *model, const QModelIndex &parent = QModelIndex() ) { const int rowCount = model->rowCount( parent ); for ( int row = 0; row < rowCount; row++ ) { QModelIndex index = model->index( row, 0, parent ); model->setData( index, Qt::Checked, Qt::CheckStateRole ); if ( model->rowCount( index ) > 0 ) { checkAllItems( model, index ); } } } void KOAlarmClient::deferredInit() { if ( !collectionsAvailable() ) { return; } kDebug(5891) << "Performing delayed initialization."; // load reminders that were active when quitting KConfigGroup genGroup( KGlobal::config(), "General" ); const int numReminders = genGroup.readEntry( "Reminders", 0 ); for ( int i=1; i<=numReminders; ++i ) { const QString group( QString::fromLatin1( "Incidence-%1" ).arg( i ) ); const KConfigGroup incGroup( KGlobal::config(), group ); const KUrl url = incGroup.readEntry( "AkonadiUrl" ); Akonadi::Item::Id akonadiItemId = -1; if ( !url.isValid() ) { - // logic to migrate old KOrganizer incidence uid's to a Akonadi item. - const QString uid = incGroup.readEntry( "UID" ); - if ( !uid.isEmpty() ) { - akonadiItemId = mCalendar->item( uid ).id(); - } + //FIXME This no longer exists anyways + // // logic to migrate old KOrganizer incidence uid's to a Akonadi item. + // const QString uid = incGroup.readEntry( "UID" ); + // if ( !uid.isEmpty() ) { + // akonadiItemId = mCalendar->item( uid ).id(); + // } } else { akonadiItemId = Akonadi::Item::fromUrl( url ).id(); } if ( akonadiItemId >= 0 ) { const QDateTime dt = incGroup.readEntry( "RemindAt", QDateTime() ); Akonadi::Item i = mCalendar->item( Akonadi::Item::fromUrl( url ).id() ); if ( CalendarSupport::hasIncidence( i ) && !CalendarSupport::incidence( i )->alarms().isEmpty() ) { createReminder( mCalendar, i, dt, QString() ); } } } KCheckableProxyModel *checkableModel = mCalendar->checkableProxyModel(); checkAllItems( checkableModel ); // Now that everything is set up, a first check for reminders can be performed. checkAlarms(); } bool KOAlarmClient::dockerEnabled() { KConfig korgConfig( KStandardDirs::locate( "config", QLatin1String("korganizerrc") ) ); KConfigGroup generalGroup( &korgConfig, "System Tray" ); return generalGroup.readEntry( "ShowReminderDaemon", true ); } bool KOAlarmClient::collectionsAvailable() const { // The list of collections must be available. if ( !mETM->isCollectionTreeFetched() ) { return false; } // All collections must be populated. const int rowCount = mETM->rowCount(); for ( int row = 0; row < rowCount; ++row ) { static const int column = 0; const QModelIndex index = mETM->index( row, column ); bool haveData = mETM->data( index, Akonadi::EntityTreeModel::IsPopulatedRole ).toBool(); if ( !haveData ) { return false; } } return true; } void KOAlarmClient::checkAlarms() { KConfigGroup cfg( KGlobal::config(), "General" ); if ( !cfg.readEntry( "Enabled", true ) ) { return; } // We do not want to miss any reminders, so don't perform check unless // the collections are available and populated. if ( !collectionsAvailable() ) { kDebug(5891) << "Collections are not available; aborting check."; return; } QDateTime from = mLastChecked.addSecs( 1 ); mLastChecked = QDateTime::currentDateTime(); kDebug(5891) << "Check:" << from.toString() << " -" << mLastChecked.toString(); const Alarm::List alarms = mCalendar->alarms( KDateTime( from, KDateTime::LocalZone ), KDateTime( mLastChecked, KDateTime::LocalZone ), true /* exclude blocked alarms */); foreach ( const Alarm::Ptr &alarm, alarms ) { - const QString uid = alarm->customProperty( "ETMCalendar", "parentUid" ); - const Akonadi::Item::Id id = mCalendar->item( uid ).id(); - const Akonadi::Item item = mCalendar->item( id ); - - createReminder( mCalendar, item, from, alarm->text() ); + QStringList calendars = mCalendar->calendars(alarm->parentUid()); + if (!calendars.isEmpty()) { + //FIXME don't just pick the first calendar + const Incidence::Ptr parentIncidence = mCalendar->incidence(calendars.first(), alarm->parentUid()); + const Akonadi::Item item = mCalendar->item( mCalendar->incidence(calendars.first(), alarm->parentUid()) ); + createReminder( mCalendar, item, from, alarm->text() ); + } else { + kWarning() << "Couldn't find calendar"; + } } } void KOAlarmClient::createReminder( const Akonadi::ETMCalendar::Ptr &calendar, const Akonadi::Item &aitem, const QDateTime &remindAtDate, const QString &displayText ) { if ( !CalendarSupport::hasIncidence( aitem ) ) { return; } #if !defined(Q_WS_MAEMO_5) && !defined(KORGAC_AKONADI_AGENT) if ( !mDialog ) { mDialog = new AlarmDialog( calendar ); connect( this, SIGNAL(saveAllSignal()), mDialog, SLOT(slotSave()) ); if ( mDocker ) { connect( mDialog, SIGNAL(reminderCount(int)), mDocker, SLOT(slotUpdate(int)) ); connect( mDocker, SIGNAL(suspendAllSignal()), mDialog, SLOT(suspendAll()) ); connect( mDocker, SIGNAL(dismissAllSignal()), mDialog, SLOT(dismissAll()) ); } } mDialog->addIncidence( aitem, remindAtDate, displayText ); mDialog->wakeUp(); #else const Incidence::Ptr incidence = CalendarSupport::incidence( aitem ); Q_UNUSED( calendar ); Q_UNUSED( remindAtDate ); Q_UNUSED( displayText ); #if defined(Q_WS_MAEMO_5) QMaemo5InformationBox::information( 0, incidence->summary(), QMaemo5InformationBox::NoTimeout ); #else KNotification *notify = new KNotification( "reminder", 0, KNotification::Persistent ); notify->setText( incidence->summary() ); notify->sendEvent(); #endif #endif saveLastCheckTime(); } void KOAlarmClient::slotQuit() { emit saveAllSignal(); saveLastCheckTime(); quit(); } void KOAlarmClient::saveLastCheckTime() { KConfigGroup cg( KGlobal::config(), "Alarms" ); cg.writeEntry( "CalendarsLastChecked", mLastChecked ); KGlobal::config()->sync(); } void KOAlarmClient::quit() { kDebug(); #if !defined(KORGAC_AKONADI_AGENT) kapp->quit(); #endif } bool KOAlarmClient::commitData( QSessionManager & ) { emit saveAllSignal(); saveLastCheckTime(); return true; } void KOAlarmClient::forceAlarmCheck() { checkAlarms(); saveLastCheckTime(); } void KOAlarmClient::dumpDebug() { KConfigGroup cfg( KGlobal::config(), "Alarms" ); const QDateTime lastChecked = cfg.readEntry( "CalendarsLastChecked", QDateTime() ); kDebug() << "Last Check:" << lastChecked; } QStringList KOAlarmClient::dumpAlarms() { const KDateTime start = KDateTime( QDateTime::currentDateTime().date(), QTime( 0, 0 ), KDateTime::LocalZone ); const KDateTime end = start.addDays( 1 ).addSecs( -1 ); QStringList lst; // Don't translate, this is for debugging purposes. lst << QLatin1String( "AlarmDeamon::dumpAlarms() from " ) + start.toString() + QLatin1String(" to ") + end.toString(); Alarm::List alarms = mCalendar->alarms( start, end ); foreach( Alarm::Ptr a, alarms ) { - const Incidence::Ptr parentIncidence = mCalendar->incidence( a->parentUid() ); - lst << QLatin1String( " " ) + parentIncidence->summary() + QLatin1String(" (") + a->time().toString() + QLatin1Char(')'); + QStringList calendars = mCalendar->calendars(a->parentUid()); + if (!calendars.isEmpty()) { + //FIXME don't just pick the first calendar + const Incidence::Ptr parentIncidence = mCalendar->incidence(calendars.first(), a->parentUid()); + lst << QLatin1String( " " ) + parentIncidence->summary() + QLatin1String(" (") + a->time().toString() + QLatin1Char(')'); + } else { + kWarning() << "Couldn't find calendar"; + } } return lst; } void KOAlarmClient::debugShowDialog() { // showAlarmDialog(); } void KOAlarmClient::hide() { #if !defined(KORGAC_AKONADI_AGENT) delete mDocker; mDocker = 0; #endif } void KOAlarmClient::show() { #if !defined(KORGAC_AKONADI_AGENT) if ( !mDocker ) { if ( dockerEnabled() ) { mDocker = new AlarmDockWindow; connect( this, SIGNAL(reminderCount(int)), mDocker, SLOT(slotUpdate(int)) ); connect( mDocker, SIGNAL(quitSignal()), SLOT(slotQuit()) ); } if ( mDialog ) { connect( mDialog, SIGNAL(reminderCount(int)), mDocker, SLOT(slotUpdate(int)) ); connect( mDocker, SIGNAL(suspendAllSignal()), mDialog, SLOT(suspendAll()) ); connect( mDocker, SIGNAL(dismissAllSignal()), mDialog, SLOT(dismissAll()) ); } } #endif } diff --git a/korganizer/calendarview.cpp b/korganizer/calendarview.cpp index dbf5f694cd..ea35010a8b 100644 --- a/korganizer/calendarview.cpp +++ b/korganizer/calendarview.cpp @@ -1,2839 +1,2839 @@ /* This file is part of KOrganizer. Copyright (c) 1997, 1998, 1999 Preston Brown Fester Zigterman Ian Dawes Laszlo Boloni Copyright (C) 2000-2004 Cornelius Schumacher Copyright (C) 2003-2004 Reinhold Kainhofer Copyright (C) 2005 Rafal Rzepecki This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #include "calendarview.h" #include "datechecker.h" #include "datenavigator.h" #include "datenavigatorcontainer.h" #include "exportwebdialog.h" #include "kocorehelper.h" #include "kodaymatrix.h" #include "kodialogmanager.h" #include "koeventviewerdialog.h" #include "koglobals.h" #include "kohelper.h" #include "koprefs.h" #include "koviewmanager.h" #include "htmlexportsettings.h" #include "navigatorbar.h" #include "views/agendaview/koagendaview.h" #include "views/monthview/monthview.h" #include "views/multiagendaview/multiagendaview.h" #include "views/todoview/kotodoview.h" #include "kocheckableproxymodel.h" #include "akonadicollectionview.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include CalendarView::CalendarView( QWidget *parent ) : CalendarViewBase( parent ), mCheckableProxyModel( 0 ), mETMCollectionView( 0 ), mSearchCollectionHelper(this) { Akonadi::Control::widgetNeedsAkonadi( this ); mChanger = new Akonadi::IncidenceChanger( new IncidenceEditorNG::IndividualMailComponentFactory( this ), this ); mChanger->setDefaultCollection( Akonadi::Collection( CalendarSupport::KCalPrefs::instance()->defaultCalendarId() ) ); mChanger->setDestinationPolicy( static_cast( KOPrefs::instance()->destination() ) ); // We reuse the EntityTreeModel from the calendar singleton to save memory. // We don't reuse the entire ETMCalendar because we want a different selection model. Checking/unchecking // calendars in korganizer shouldn't affect kontact's summary view mCalendar = Akonadi::ETMCalendar::Ptr( new Akonadi::ETMCalendar( CalendarSupport::calendarSingleton() ) ); mCalendar->setObjectName( QLatin1String("KOrg Calendar") ); mCalendarClipboard = new Akonadi::CalendarClipboard( mCalendar, mChanger, this ); mITIPHandler = new Akonadi::ITIPHandler( this ); mITIPHandler->setCalendar( mCalendar ); connect( mCalendarClipboard, SIGNAL(cutFinished(bool,QString)), SLOT(onCutFinished()) ); Akonadi::AttributeFactory::registerAttribute(); mViewManager = new KOViewManager( this ); mDialogManager = new KODialogManager( this ); mTodoPurger = new Akonadi::TodoPurger( this ); mTodoPurger->setCalendar( mCalendar ); mTodoPurger->setIncidenceChager( mChanger ); connect(mTodoPurger, SIGNAL(todosPurged(bool,int,int)), SLOT(onTodosPurged(bool,int,int))); mReadOnly = false; mSplitterSizesValid = false; mCalPrinter = 0; mDateNavigator = new DateNavigator( this ); mDateChecker = new DateChecker( this ); QVBoxLayout *topLayout = new QVBoxLayout( this ); topLayout->setMargin( 0 ); // create the main layout frames. mPanner = new QSplitter( Qt::Horizontal, this ); mPanner->setObjectName( QLatin1String("CalendarView::Panner") ); topLayout->addWidget( mPanner ); mLeftSplitter = new QSplitter( Qt::Vertical, mPanner ); mLeftSplitter->setObjectName( QLatin1String("CalendarView::LeftFrame") ); // The GUI checkboxes of "show widget XYZ" are confusing when the QSplitter // hides the widget magically. I know I blamed Akonadi for not showing my // calendar more than once. mLeftSplitter->setChildrenCollapsible( false ); mDateNavigatorContainer = new DateNavigatorContainer( mLeftSplitter ); mDateNavigatorContainer->setObjectName( QLatin1String("CalendarView::DateNavigator") ); mTodoList = new KOTodoView( true/*sidebar*/, mLeftSplitter ); mTodoList->setObjectName(QLatin1String( "todolist") ); mEventViewerBox = new KVBox( mLeftSplitter ); mEventViewerBox->setMargin( 0 ); mEventViewer = new CalendarSupport::IncidenceViewer( mCalendar.data(), mEventViewerBox ); mEventViewer->setObjectName( QLatin1String("EventViewer") ); KVBox *rightBox = new KVBox( mPanner ); rightBox->layout()->setMargin( 0 ); mNavigatorBar = new NavigatorBar( rightBox ); mRightFrame = new QStackedWidget( rightBox ); mMessageWidget = new CalendarSupport::MessageWidget( rightBox ); rightBox->setStretchFactor( mRightFrame, 1 ); mLeftFrame = mLeftSplitter; mLeftFrame->installEventFilter( this ); mChanger->setGroupwareCommunication( CalendarSupport::KCalPrefs::instance()->useGroupwareCommunication() ); connect( mChanger, SIGNAL(createFinished(int,Akonadi::Item,Akonadi::IncidenceChanger::ResultCode,QString)), SLOT(slotCreateFinished(int,Akonadi::Item,Akonadi::IncidenceChanger::ResultCode,QString)) ); connect( mChanger, SIGNAL(deleteFinished(int,QVector,Akonadi::IncidenceChanger::ResultCode,QString)), SLOT(slotDeleteFinished(int,QVector,Akonadi::IncidenceChanger::ResultCode,QString)) ); connect( mChanger, SIGNAL(modifyFinished(int,Akonadi::Item,Akonadi::IncidenceChanger::ResultCode,QString)), SLOT(slotModifyFinished(int,Akonadi::Item,Akonadi::IncidenceChanger::ResultCode,QString)) ); // Signals emitted by mDateNavigator connect( mDateNavigator, SIGNAL(datesSelected(KCalCore::DateList,QDate)), SLOT(showDates(KCalCore::DateList,QDate)) ); connect( mDateNavigatorContainer, SIGNAL(newEventSignal(QDate)), SLOT(newEvent(QDate)) ); connect( mDateNavigatorContainer, SIGNAL(newTodoSignal(QDate)), SLOT(newTodo(QDate)) ); connect( mDateNavigatorContainer, SIGNAL(newJournalSignal(QDate)), SLOT(newJournal(QDate)) ); // Signals emitted by mNavigatorBar connect( mNavigatorBar, SIGNAL(prevYearClicked()), mDateNavigator, SLOT(selectPreviousYear()) ); connect( mNavigatorBar, SIGNAL(nextYearClicked()), mDateNavigator, SLOT(selectNextYear()) ); connect( mNavigatorBar, SIGNAL(prevMonthClicked()), mDateNavigator, SLOT(selectPreviousMonth()) ); connect( mNavigatorBar, SIGNAL(nextMonthClicked()), mDateNavigator, SLOT(selectNextMonth()) ); connect( mNavigatorBar, SIGNAL(monthSelected(int)), mDateNavigator, SLOT(selectMonth(int)) ); connect( mNavigatorBar, SIGNAL(yearSelected(int)), mDateNavigator, SLOT(selectYear(int)) ); // Signals emitted by mDateNavigatorContainer connect( mDateNavigatorContainer, SIGNAL(weekClicked(QDate,QDate)), SLOT(selectWeek(QDate,QDate)) ); connect( mDateNavigatorContainer, SIGNAL(prevMonthClicked(QDate,QDate,QDate)), mDateNavigator, SLOT(selectPreviousMonth(QDate,QDate,QDate)) ); connect( mDateNavigatorContainer, SIGNAL(nextMonthClicked(QDate,QDate,QDate)), mDateNavigator, SLOT(selectNextMonth(QDate,QDate,QDate)) ); connect( mDateNavigatorContainer, SIGNAL(prevYearClicked()), mDateNavigator, SLOT(selectPreviousYear()) ); connect( mDateNavigatorContainer, SIGNAL(nextYearClicked()), mDateNavigator, SLOT(selectNextYear()) ); connect( mDateNavigatorContainer, SIGNAL(monthSelected(int)), mDateNavigator, SLOT(selectMonth(int)) ); connect( mDateNavigatorContainer, SIGNAL(yearSelected(int)), mDateNavigator, SLOT(selectYear(int)) ); connect( mDateNavigatorContainer, SIGNAL(goPrevious()), mDateNavigator, SLOT(selectPrevious()) ); connect( mDateNavigatorContainer, SIGNAL(goNext()), mDateNavigator, SLOT(selectNext()) ); connect( mDateNavigatorContainer, SIGNAL(datesSelected(KCalCore::DateList,QDate)), mDateNavigator, SLOT(selectDates(KCalCore::DateList,QDate)) ); connect( mViewManager, SIGNAL(datesSelected(KCalCore::DateList)), mDateNavigator, SLOT(selectDates(KCalCore::DateList)) ); connect( mDateNavigatorContainer, SIGNAL(incidenceDropped(Akonadi::Item,QDate)), SLOT(addIncidenceOn(Akonadi::Item,QDate)) ); connect( mDateNavigatorContainer, SIGNAL(incidenceDroppedMove(Akonadi::Item,QDate)), SLOT(moveIncidenceTo(Akonadi::Item,QDate)) ); connect( mDateChecker, SIGNAL(dayPassed(QDate)), mTodoList, SLOT(dayPassed(QDate)) ); connect( mDateChecker, SIGNAL(dayPassed(QDate)), SIGNAL(dayPassed(QDate)) ); connect( mDateChecker, SIGNAL(dayPassed(QDate)), mDateNavigatorContainer, SLOT(updateToday()) ); connect( this, SIGNAL(configChanged()), mDateNavigatorContainer, SLOT(updateConfig()) ); connect( this, SIGNAL(incidenceSelected(Akonadi::Item,QDate)), mEventViewer, SLOT(setIncidence(Akonadi::Item,QDate)) ); //TODO: do a pretty Summary, QString s; s = i18n( "

    No Item Selected

    " "

    Select an event, to-do or journal entry to view its details " "here.

    " ); mEventViewer->setDefaultMessage( s ); mEventViewer->setWhatsThis( i18n( "View the details of events, journal entries or to-dos " "selected in KOrganizer's main view here." ) ); mEventViewer->setIncidence( Akonadi::Item(), QDate() ); mViewManager->connectTodoView( mTodoList ); mViewManager->connectView( mTodoList ); KOGlobals::self()->setHolidays( new KHolidays::HolidayRegion( KOPrefs::instance()->mHolidays ) ); connect( QApplication::clipboard(), SIGNAL(dataChanged()), SLOT(checkClipboard()) ); connect( mTodoList, SIGNAL(incidenceSelected(Akonadi::Item,QDate)), this, SLOT(processTodoListSelection(Akonadi::Item,QDate)) ); disconnect( mTodoList, SIGNAL(incidenceSelected(Akonadi::Item,QDate)), this, SLOT(processMainViewSelection(Akonadi::Item,QDate)) ); { static bool pageRegistered = false; if ( !pageRegistered ) { Akonadi::CollectionPropertiesDialog::registerPage( new CalendarSupport::CollectionGeneralPageFactory ); Akonadi::CollectionPropertiesDialog::registerPage( new PimCommon::CollectionAclPageFactory ); pageRegistered = true; } } Akonadi::FreeBusyManager::self()->setCalendar( mCalendar ); mCalendar->registerObserver( this ); mDateNavigatorContainer->setCalendar( mCalendar ); mTodoList->setCalendar( mCalendar ); mEventViewer->setCalendar( mCalendar.data() ); } CalendarView::~CalendarView() { mCalendar->unregisterObserver( this ); mCalendar->setFilter( 0 ); // So calendar doesn't deleted it twice qDeleteAll( mFilters ); qDeleteAll( mExtensions ); delete mDialogManager; delete mViewManager; delete mEventViewer; } Akonadi::ETMCalendar::Ptr CalendarView::calendar() const { return mCalendar; } QDate CalendarView::activeDate( bool fallbackToToday ) { KOrg::BaseView *curView = mViewManager->currentView(); if ( curView ) { if ( curView->selectionStart().isValid() ) { return curView->selectionStart().date(); } // Try the view's selectedIncidenceDates() if ( !curView->selectedIncidenceDates().isEmpty() ) { if ( curView->selectedIncidenceDates().first().isValid() ) { return curView->selectedIncidenceDates().first(); } } } // When all else fails, use the navigator start date, or today. if ( fallbackToToday ) { return QDate::currentDate(); } else { return mDateNavigator->selectedDates().first(); } } QDate CalendarView::activeIncidenceDate() { KOrg::BaseView *curView = mViewManager->currentView(); if ( curView ) { KCalCore::DateList dates = curView->selectedIncidenceDates(); if ( !dates.isEmpty() ) { return dates.first(); } } return QDate(); } QDate CalendarView::startDate() { KCalCore::DateList dates = mDateNavigator->selectedDates(); return dates.first(); } QDate CalendarView::endDate() { KCalCore::DateList dates = mDateNavigator->selectedDates(); return dates.last(); } void CalendarView::createPrinter() { if ( !mCalPrinter ) { mCalPrinter = new CalendarSupport::CalPrinter( this, mCalendar ); connect( this, SIGNAL(configChanged()), mCalPrinter, SLOT(updateConfig()) ); } } bool CalendarView::saveCalendar( const QString &filename ) { // Store back all unsaved data into calendar object mViewManager->currentView()->flushView(); KCalCore::FileStorage storage( mCalendar ); storage.setFileName( filename ); storage.setSaveFormat( new KCalCore::ICalFormat ); return storage.save(); } void CalendarView::archiveCalendar() { mDialogManager->showArchiveDialog(); } void CalendarView::readSettings() { QString str; // read settings from the KConfig, supplying reasonable // defaults where none are to be found KConfig *config = KOGlobals::self()->config(); KConfigGroup geometryConfig( config, "KOrganizer Geometry" ); QList sizes = geometryConfig.readEntry( "Separator1",QList() ); if ( sizes.count() != 2 || sizes.count() == sizes.count( 0 ) ) { sizes << mDateNavigatorContainer->minimumSizeHint().width(); sizes << 300; } mPanner->setSizes( sizes ); sizes = geometryConfig.readEntry( "Separator2", QList() ); if ( !sizes.isEmpty() && sizes.count() != sizes.count( 0 ) ) { mLeftSplitter->setSizes( sizes ); } mViewManager->readSettings( config ); mTodoList->restoreLayout( config, QLatin1String( "Sidebar Todo View" ), true ); readFilterSettings( config ); KConfigGroup viewConfig( config, "Views" ); int dateCount = viewConfig.readEntry( "ShownDatesCount", 7 ); if ( dateCount == 7 ) { mDateNavigator->selectWeek(); } else { mDateNavigator->selectDates( mDateNavigator->selectedDates().first(), dateCount ); } } void CalendarView::writeSettings() { KConfig *config = KOGlobals::self()->config(); KConfigGroup geometryConfig( config, "KOrganizer Geometry" ); QList list = mMainSplitterSizes.isEmpty() ? mPanner->sizes() : mMainSplitterSizes; // splitter sizes are invalid (all zero) unless we have been shown once if ( list.count() != list.count( 0 ) && mSplitterSizesValid ) { geometryConfig.writeEntry( "Separator1", list ); } list = mLeftSplitter->sizes(); if ( list.count() != list.count( 0 ) && mSplitterSizesValid ) { geometryConfig.writeEntry( "Separator2", list ); } mViewManager->writeSettings( config ); mTodoList->saveLayout( config, QLatin1String( "Sidebar Todo View" ) ); Akonadi::CalendarSettings::self()->writeConfig(); KOPrefs::instance()->writeConfig(); CalendarSupport::KCalPrefs::instance()->writeConfig(); writeFilterSettings( config ); KConfigGroup viewConfig( config, "Views" ); viewConfig.writeEntry( "ShownDatesCount", mDateNavigator->selectedDates().count() ); config->sync(); } void CalendarView::readFilterSettings( KConfig *config ) { qDeleteAll( mFilters ); mFilters.clear(); KConfigGroup generalConfig( config, "General" ); // FIXME: Move the filter loading and saving to the CalFilter class in libkcal QStringList filterList = generalConfig.readEntry( "CalendarFilters", QStringList() ); QString currentFilter = generalConfig.readEntry( "Current Filter" ); QStringList::ConstIterator it = filterList.constBegin(); QStringList::ConstIterator end = filterList.constEnd(); while ( it != end ) { KCalCore::CalFilter *filter = new KCalCore::CalFilter( *it ); KConfigGroup filterConfig( config, QLatin1String("Filter_") + (*it) ); filter->setCriteria( filterConfig.readEntry( "Criteria", 0 ) ); filter->setCategoryList( filterConfig.readEntry( "CategoryList", QStringList() ) ); if ( filter->criteria() & KCalCore::CalFilter::HideNoMatchingAttendeeTodos ) { filter->setEmailList( CalendarSupport::KCalPrefs::instance()->allEmails() ); } filter->setCompletedTimeSpan( filterConfig.readEntry( "HideTodoDays", 0 ) ); mFilters.append( filter ); ++it; } int pos = filterList.indexOf( currentFilter ); mCurrentFilter = 0; if ( pos >= 0 ) { mCurrentFilter = mFilters.at( pos ); } updateFilter(); } void CalendarView::writeFilterSettings( KConfig *config ) { QStringList filterList; foreach ( KCalCore::CalFilter *filter, mFilters ) { filterList << filter->name(); KConfigGroup filterConfig( config, QLatin1String("Filter_") + filter->name() ); filterConfig.writeEntry( "Criteria", filter->criteria() ); filterConfig.writeEntry( "CategoryList", filter->categoryList() ); filterConfig.writeEntry( "HideTodoDays", filter->completedTimeSpan() ); } KConfigGroup generalConfig( config, "General" ); generalConfig.writeEntry( "CalendarFilters", filterList ); if ( mCurrentFilter ) { generalConfig.writeEntry( "Current Filter", mCurrentFilter->name() ); } else { generalConfig.writeEntry( "Current Filter", QString() ); } } void CalendarView::goDate( const QDate &date ) { mDateNavigator->selectDate( date ); } void CalendarView::showDate( const QDate &date ) { int dateCount = mDateNavigator->datesCount(); if ( dateCount == 7 ) { mDateNavigator->selectWeek( date ); } else { mDateNavigator->selectDates( date, dateCount ); } } void CalendarView::goToday() { mDateNavigator->selectToday(); } void CalendarView::goNext() { if ( dynamic_cast( mViewManager->currentView() ) ) { const QDate month = mDateNavigatorContainer->monthOfNavigator( 0 ); QPair limits = KODayMatrix::matrixLimits( month ); mDateNavigator->selectNextMonth( month, limits.first, limits.second ); } else { mDateNavigator->selectNext(); } } void CalendarView::goPrevious() { if ( dynamic_cast( mViewManager->currentView() ) ) { const QDate month = mDateNavigatorContainer->monthOfNavigator( 0 ); QPair limits = KODayMatrix::matrixLimits( month ); mDateNavigator->selectPreviousMonth( month, limits.first, limits.second ); } else { mDateNavigator->selectPrevious(); } } void CalendarView::updateConfig() { updateConfig( QByteArray( "korganizer" ) ); } void CalendarView::updateConfig( const QByteArray &receiver ) { if ( receiver != "korganizer" ) { return; } if ( mCalPrinter ) { mCalPrinter->deleteLater(); mCalPrinter = 0; } KOGlobals::self()->setHolidays( new KHolidays::HolidayRegion( KOPrefs::instance()->mHolidays ) ); // Only set a new time zone if it changed. This prevents the window // from being modified on start KDateTime::Spec newTimeSpec = CalendarSupport::KCalPrefs::instance()->timeSpec(); if ( mCalendar->viewTimeSpec() != newTimeSpec ) { const QString question( i18n( "The time zone setting was changed. " "Do you want to keep the absolute time of " "the items in your calendar, which will show " "them to be at a different time than " "before, or move them to be at the old time " "also in the new time zone?" ) ); int rc = KMessageBox::questionYesNo( this, question, i18n( "Keep Absolute Times?" ), KGuiItem( i18n( "Keep Times" ) ), KGuiItem( i18n( "Move Times" ) ), QLatin1String("calendarKeepAbsoluteTimes") ); if ( rc == KMessageBox::Yes ) { // keep the absolute time - note the new viewing time zone in the calendar mCalendar->setViewTimeSpec( newTimeSpec ); } else { // only set the new timezone, wihtout shifting events, they will be // interpreted as being in the new timezone now mCalendar->shiftTimes( mCalendar->viewTimeSpec(), newTimeSpec ); } } // config changed lets tell the date navigator the new modes // if there weren't changed they are ignored updateHighlightModes(); emit configChanged(); //switch beetween merged, side by side and tabbed agenda if needed mViewManager->updateMultiCalendarDisplay(); // To make the "fill window" configurations work mViewManager->raiseCurrentView(); mChanger->setDestinationPolicy( static_cast( KOPrefs::instance()->destination() ) ); mChanger->setGroupwareCommunication( CalendarSupport::KCalPrefs::instance()->useGroupwareCommunication() ); } void CalendarView::slotCreateFinished( int changeId, const Akonadi::Item &item, Akonadi::IncidenceChanger::ResultCode resultCode, const QString &errorString ) { Q_UNUSED( changeId ); if ( resultCode == Akonadi::IncidenceChanger::ResultCodeSuccess ) { changeIncidenceDisplay( item, Akonadi::IncidenceChanger::ChangeTypeCreate ); updateUnmanagedViews(); checkForFilteredChange( item ); } else { kError() << "Incidence not added, job reported error: " << errorString; } } void CalendarView::slotModifyFinished( int changeId, const Akonadi::Item &item, Akonadi::IncidenceChanger::ResultCode resultCode, const QString &errorString ) { Q_UNUSED( changeId ); if ( resultCode != Akonadi::IncidenceChanger::ResultCodeSuccess ) { kError() << "Incidence not modified, job reported error: " << errorString; return; } Q_ASSERT( item.isValid() ); KCalCore::Incidence::Ptr incidence = CalendarSupport::incidence( item ); Q_ASSERT( incidence ); QSet dirtyFields = incidence->dirtyFields(); incidence->resetDirtyFields(); // Record completed todos in journals, if enabled. we should to this here in // favor of the todolist. users can mark a task as completed in an editor // as well. if ( incidence->type() == KCalCore::Incidence::TypeTodo && KOPrefs::instance()->recordTodosInJournals() && ( dirtyFields.contains( KCalCore::Incidence::FieldCompleted ) ) ) { KCalCore::Todo::Ptr todo = incidence.dynamicCast(); if ( todo->isCompleted() || todo->recurs() ) { QString timeStr = KGlobal::locale()->formatTime( QTime::currentTime() ); QString description = i18n( "Todo completed: %1 (%2)", incidence->summary(), timeStr ); KCalCore::Journal::List journals = calendar()->journals( QDate::currentDate() ); if ( journals.isEmpty() ) { KCalCore::Journal::Ptr journal( new KCalCore::Journal ); journal->setDtStart( KDateTime::currentDateTime( CalendarSupport::KCalPrefs::instance()->timeSpec() ) ); QString dateStr = KGlobal::locale()->formatDate( QDate::currentDate() ); journal->setSummary( i18n( "Journal of %1", dateStr ) ); journal->setDescription( description ); if ( mChanger->createIncidence( journal, item.parentCollection(), this ) == -1 ) { kError() << "Unable to add Journal"; return; } } else { // journal list is not empty - Akonadi::Item journalItem = mCalendar->item( journals.first()->uid() ); + Akonadi::Item journalItem = mCalendar->item( journals.first() ); KCalCore::Journal::Ptr journal = CalendarSupport::journal( journalItem ); KCalCore::Journal::Ptr oldJournal( journal->clone() ); journal->setDescription( journal->description().append( QLatin1Char('\n') + description ) ); mChanger->modifyIncidence( journalItem, oldJournal, this ); } } } changeIncidenceDisplay( item, Akonadi::IncidenceChanger::ChangeTypeCreate ); updateUnmanagedViews(); checkForFilteredChange( item ); } void CalendarView::slotDeleteFinished( int changeId, const QVector &itemIdList, Akonadi::IncidenceChanger::ResultCode resultCode, const QString &errorString ) { Q_UNUSED( changeId ); if ( resultCode == Akonadi::IncidenceChanger::ResultCodeSuccess ) { foreach( Akonadi::Item::Id id, itemIdList ) { Akonadi::Item item = mCalendar->item( id ); if ( item.isValid() ) changeIncidenceDisplay( item, Akonadi::IncidenceChanger::ChangeTypeDelete ); } updateUnmanagedViews(); } else { kError() << "Incidence not deleted, job reported error: " << errorString; } } void CalendarView::checkForFilteredChange( const Akonadi::Item &item ) { KCalCore::Incidence::Ptr incidence = CalendarSupport::incidence( item ); KCalCore::CalFilter *filter = calendar()->filter(); if ( filter && !filter->filterIncidence( incidence ) ) { // Incidence is filtered and thus not shown in the view, tell the // user so that he isn't surprised if his new event doesn't show up mMessageWidget->setText( i18n( "The item \"%1\" is filtered by your current filter rules, " "so it will be hidden and not appear in the view.", incidence->summary() ) ); mMessageWidget->show(); } } void CalendarView::startMultiModify( const QString &text ) { mChanger->startAtomicOperation( text ); } void CalendarView::endMultiModify() { mChanger->endAtomicOperation(); } void CalendarView::changeIncidenceDisplay( const Akonadi::Item &item, Akonadi::IncidenceChanger::ChangeType changeType ) { if ( mDateNavigatorContainer->isVisible() ) { mDateNavigatorContainer->updateView(); } mDialogManager->updateSearchDialog(); if ( CalendarSupport::hasIncidence( item ) ) { // If there is an event view visible update the display mViewManager->currentView()->changeIncidenceDisplay( item, changeType ); } else { mViewManager->currentView()->updateView(); } } void CalendarView::updateView( const QDate &start, const QDate &end, const QDate &preferredMonth, const bool updateTodos ) { const bool currentViewIsTodoView = mViewManager->currentView()->identifier() == "DefaultTodoView"; if ( updateTodos && !currentViewIsTodoView && mTodoList->isVisible() ) { // Update the sidepane todoView mTodoList->updateView(); } if ( start.isValid() && end.isValid() && !( currentViewIsTodoView && !updateTodos ) ) { mViewManager->updateView( start, end, preferredMonth ); } if ( mDateNavigatorContainer->isVisible() ) { mDateNavigatorContainer->updateView(); } } void CalendarView::updateView() { const KCalCore::DateList tmpList = mDateNavigator->selectedDates(); const QDate month = mDateNavigatorContainer->monthOfNavigator(); // We assume that the navigator only selects consecutive days. updateView( tmpList.first(), tmpList.last(), month/**preferredMonth*/ ); } void CalendarView::updateUnmanagedViews() { if ( mDateNavigatorContainer->isVisible() ) { mDateNavigatorContainer->updateDayMatrix(); } } int CalendarView::msgItemDelete( const Akonadi::Item &item ) { return KMessageBox::warningContinueCancel( this, i18nc( "@info", "Do you really want to permanently remove the item \"%1\"?", CalendarSupport::incidence( item )->summary() ), i18nc( "@title:window", "Delete Item?" ), KStandardGuiItem::del() ); } void CalendarView::edit_cut() { const Akonadi::Item item = selectedIncidence(); KCalCore::Incidence::Ptr incidence = CalendarSupport::incidence( item ); if ( !incidence ) { kError() << "Null incidence"; return; } mCalendarClipboard->cutIncidence( incidence, Akonadi::CalendarClipboard::AskMode ); } void CalendarView::edit_copy() { const Akonadi::Item item = selectedIncidence(); if ( !item.isValid() ) { KNotification::beep(); kError() << "Invalid item"; return; } KCalCore::Incidence::Ptr incidence = CalendarSupport::incidence( item ); Q_ASSERT( incidence ); if ( !mCalendarClipboard->copyIncidence( incidence, Akonadi::CalendarClipboard::AskMode ) ) kError() << "Error copying incidence"; checkClipboard(); } void CalendarView::edit_paste() { // If in agenda and month view, use the selected time and date from there. // In all other cases, use the navigator's selected date. QDateTime endDT; KDateTime finalDateTime; bool useEndTime = false; KCalUtils::DndFactory::PasteFlags pasteFlags = 0; KOrg::BaseView *curView = mViewManager->currentView(); KOAgendaView *agendaView = mViewManager->agendaView(); MonthView *monthView = mViewManager->monthView(); if ( !curView ) { kWarning() << "No view is selected, can't paste"; return; } if ( curView == agendaView && agendaView->selectionStart().isValid() ) { const QDate date = agendaView->selectionStart().date(); endDT = agendaView->selectionEnd(); useEndTime = !agendaView->selectedIsSingleCell(); if ( agendaView->selectedIsAllDay() ) { finalDateTime = KDateTime( date ); } else { finalDateTime = KDateTime( date, agendaView->selectionStart().time() ); } } else if ( curView == monthView && monthView->selectionStart().isValid() ) { finalDateTime = KDateTime( monthView->selectionStart().date() ); pasteFlags = KCalUtils::DndFactory::FlagPasteAtOriginalTime; } else if ( !mDateNavigator->selectedDates().isEmpty() && curView->supportsDateNavigation() ) { // default to the selected date from the navigator finalDateTime = KDateTime( mDateNavigator->selectedDates().first() ); pasteFlags = KCalUtils::DndFactory::FlagPasteAtOriginalTime; } if ( !finalDateTime.isValid() && curView->supportsDateNavigation() ) { KMessageBox::sorry( this, i18n( "Paste failed: unable to determine a valid target date." ) ); return; } KCalUtils::DndFactory factory( mCalendar ); KCalCore::Incidence::List pastedIncidences = factory.pasteIncidences( finalDateTime, pasteFlags ); KCalCore::Incidence::List::Iterator it; for ( it = pastedIncidences.begin(); it != pastedIncidences.end(); ++it ) { // FIXME: use a visitor here if ( ( *it )->type() == KCalCore::Incidence::TypeEvent ) { KCalCore::Event::Ptr pastedEvent = ( *it ).staticCast(); // only use selected area if event is of the same type (all-day or non-all-day // as the current selection is if ( agendaView && endDT.isValid() && useEndTime ) { if ( ( pastedEvent->allDay() && agendaView->selectedIsAllDay() ) || ( !pastedEvent->allDay() && !agendaView->selectedIsAllDay() ) ) { KDateTime kdt( endDT, CalendarSupport::KCalPrefs::instance()->timeSpec() ); pastedEvent->setDtEnd( kdt.toTimeSpec( pastedEvent->dtEnd().timeSpec() ) ); } } pastedEvent->setRelatedTo( QString() ); mChanger->createIncidence( KCalCore::Event::Ptr( pastedEvent->clone() ), Akonadi::Collection(), this ); } else if ( ( *it )->type() == KCalCore::Incidence::TypeTodo ) { KCalCore::Todo::Ptr pastedTodo = ( *it ).staticCast(); Akonadi::Item _selectedTodoItem = selectedTodo(); // if we are cutting a hierarchy only the root // should be son of _selectedTodo KCalCore::Todo::Ptr _selectedTodo = CalendarSupport::todo( _selectedTodoItem ); if ( _selectedTodo && pastedTodo->relatedTo().isEmpty() ) { pastedTodo->setRelatedTo( _selectedTodo->uid() ); } // When pasting multiple incidences, don't ask which collection to use, for each one mChanger->createIncidence( KCalCore::Todo::Ptr( pastedTodo->clone() ), Akonadi::Collection(), this ); } else if ( ( *it )->type() == KCalCore::Incidence::TypeJournal ) { // When pasting multiple incidences, don't ask which collection to use, for each one mChanger->createIncidence( KCalCore::Incidence::Ptr( ( *it )->clone() ), Akonadi::Collection(), this ); } } } void CalendarView::edit_options() { mDialogManager->showOptionsDialog(); } void CalendarView::dateTimesForNewEvent( QDateTime &startDt, QDateTime &endDt, bool &allDay ) { mViewManager->currentView()->eventDurationHint( startDt, endDt, allDay ); if ( !startDt.isValid() || !endDt.isValid() ) { startDt.setDate( activeDate( true ) ); startDt.setTime( CalendarSupport::KCalPrefs::instance()->mStartTime.time() ); int addSecs = ( CalendarSupport::KCalPrefs::instance()->mDefaultDuration.time().hour() * 3600 ) + ( CalendarSupport::KCalPrefs::instance()->mDefaultDuration.time().minute() * 60 ); endDt = startDt.addSecs( addSecs ); } } IncidenceEditorNG::IncidenceDialog *CalendarView::incidenceDialog(const Akonadi::Item &item) { IncidenceEditorNG::IncidenceDialog *dialog = mDialogManager->createDialog(item); connect(dialog, SIGNAL(incidenceCreated(Akonadi::Item)), SLOT(handleIncidenceCreated(Akonadi::Item))); return dialog; } IncidenceEditorNG::IncidenceDialog *CalendarView::newEventEditor( const KCalCore::Event::Ptr &event ) { Akonadi::Item item; item.setPayload( event ); IncidenceEditorNG::IncidenceDialog *dialog = incidenceDialog( item ); dialog->load( item ); mDialogManager->connectTypeAhead( dialog, qobject_cast( viewManager()->currentView() ) ); return dialog; } void CalendarView::newEvent() { newEvent( QDateTime(), QDateTime() ); } void CalendarView::newEvent( const QDate &dt ) { QDateTime startDt( dt, CalendarSupport::KCalPrefs::instance()->mStartTime.time() ); QTime duration = CalendarSupport::KCalPrefs::instance()->defaultDuration().time(); QTime time = startDt.time(); time = time.addSecs( duration.hour() * 3600 + duration.minute() * 60 + duration.second() ); QDateTime endDt( startDt ); endDt.setTime( time ); newEvent( startDt, endDt ); } void CalendarView::newEvent( const QDateTime &startDt ) { newEvent( startDt, startDt ); } void CalendarView::newEvent( const QDateTime &startDtParam, const QDateTime &endDtParam, bool allDay ) { // Let the current view change the default start/end datetime QDateTime startDt( startDtParam ); QDateTime endDt( endDtParam ); // Adjust the start/end date times (i.e. replace invalid values by defaults, // and let the view adjust the type. dateTimesForNewEvent( startDt, endDt, allDay ); IncidenceEditorNG::IncidenceDefaults defaults = IncidenceEditorNG::IncidenceDefaults::minimalIncidenceDefaults(); defaults.setStartDateTime( KDateTime( startDt ) ); defaults.setEndDateTime( KDateTime( endDt ) ); KCalCore::Event::Ptr event( new KCalCore::Event ); defaults.setDefaults( event ); event->setAllDay( allDay ); IncidenceEditorNG::IncidenceDialog *eventEditor = newEventEditor( event ); Q_ASSERT( eventEditor ); // Fallsback to the default collection defined in config eventEditor->selectCollection( defaultCollection( KCalCore::Event::eventMimeType() ) ); } void CalendarView::newEvent( const QString &summary, const QString &description, const QStringList &attachments, const QStringList &attendees, const QStringList &attachmentMimetypes, bool inlineAttachment ) { // Adjust the start/end date times (i.e. replace invalid values by defaults, // and let the view adjust the type. QDateTime startDt; QDateTime endDt; bool allDay = false; dateTimesForNewEvent( startDt, endDt, allDay ); IncidenceEditorNG::IncidenceDefaults defaults = IncidenceEditorNG::IncidenceDefaults::minimalIncidenceDefaults(); defaults.setStartDateTime( KDateTime( startDt ) ); defaults.setEndDateTime( KDateTime( endDt ) ); // if attach or attendee list is empty, these methods don't do anything, so // it's safe to call them in every case defaults.setAttachments( attachments, attachmentMimetypes, QStringList(), inlineAttachment ); defaults.setAttendees( attendees ); KCalCore::Event::Ptr event( new KCalCore::Event ); defaults.setDefaults( event ); event->setSummary( summary ); event->setDescription( description ); event->setAllDay( allDay ); newEventEditor( event ); } void CalendarView::newTodo( const QString &summary, const QString &description, const QStringList &attachments, const QStringList &attendees, const QStringList &attachmentMimetypes, bool inlineAttachment ) { Akonadi::Collection defaultCol = defaultCollection( KCalCore::Todo::todoMimeType() ); IncidenceEditorNG::IncidenceDialogFactory::createTodoEditor( summary, description, attachments, attendees, attachmentMimetypes, QStringList()/* attachment labels */, inlineAttachment, defaultCol, this/* parent */ ); } void CalendarView::newTodo() { newTodo( Akonadi::Collection() ); } void CalendarView::newTodo( const Akonadi::Collection &collection ) { IncidenceEditorNG::IncidenceDefaults defaults = IncidenceEditorNG::IncidenceDefaults::minimalIncidenceDefaults(); bool allDay = true; if ( mViewManager->currentView()->isEventView() ) { QDateTime startDt; QDateTime endDt; dateTimesForNewEvent( startDt, endDt, allDay ); defaults.setStartDateTime( KDateTime( startDt ) ); defaults.setEndDateTime( KDateTime( endDt ) ); } KCalCore::Todo::Ptr todo( new KCalCore::Todo ); defaults.setDefaults( todo ); todo->setAllDay( allDay ); Akonadi::Item item; item.setPayload( todo ); IncidenceEditorNG::IncidenceDialog *dialog = createIncidenceEditor( item, collection ); dialog->load( item ); } void CalendarView::newTodo( const QDate &date ) { IncidenceEditorNG::IncidenceDefaults defaults = IncidenceEditorNG::IncidenceDefaults::minimalIncidenceDefaults(); defaults.setEndDateTime( KDateTime( date, QTime::currentTime() ) ); KCalCore::Todo::Ptr todo( new KCalCore::Todo ); defaults.setDefaults( todo ); todo->setAllDay( true ); Akonadi::Item item; item.setPayload( todo ); IncidenceEditorNG::IncidenceDialog *dialog = createIncidenceEditor( item ); dialog->load( item ); } void CalendarView::newJournal() { newJournal( QString(), activeDate( true ) ); } void CalendarView::newJournal( const QDate &date ) { newJournal( QString(), date.isValid() ? date : activeDate( true ) ); } void CalendarView::newJournal( const Akonadi::Collection &collection ) { IncidenceEditorNG::IncidenceDefaults defaults = IncidenceEditorNG::IncidenceDefaults::minimalIncidenceDefaults(); if ( mViewManager->currentView()->isEventView() ) { QDateTime startDt; QDateTime endDt; bool allDay = true; dateTimesForNewEvent( startDt, endDt, allDay ); defaults.setStartDateTime( KDateTime( startDt ) ); defaults.setEndDateTime( KDateTime( endDt ) ); } KCalCore::Journal::Ptr journal( new KCalCore::Journal ); defaults.setDefaults( journal ); Akonadi::Item item; item.setPayload( journal ); IncidenceEditorNG::IncidenceDialog *dialog = createIncidenceEditor( item, collection ); dialog->load( item ); } void CalendarView::newJournal( const QString &text, const QDate &date ) { IncidenceEditorNG::IncidenceDefaults defaults = IncidenceEditorNG::IncidenceDefaults::minimalIncidenceDefaults(); KCalCore::Journal::Ptr journal( new KCalCore::Journal ); defaults.setStartDateTime( KDateTime( date ) ); defaults.setDefaults( journal ); journal->setSummary( text ); Akonadi::Item item; item.setPayload( journal ); IncidenceEditorNG::IncidenceDialog *dialog = createIncidenceEditor( item ); dialog->load( item ); } KOrg::BaseView *CalendarView::currentView() const { return mViewManager->currentView(); } void CalendarView::configureCurrentView() { KOrg::BaseView *const view = currentView(); if ( view && view->hasConfigurationDialog() ) { view->showConfigurationDialog( this ); } } void CalendarView::newSubTodo() { const Akonadi::Item item = selectedTodo(); if ( CalendarSupport::hasTodo( item ) ) { newSubTodo( item ); } } void CalendarView::newSubTodo( const Akonadi::Collection &collection ) { if ( !CalendarSupport::hasTodo( selectedTodo() ) ) { kWarning() << "CalendarSupport::hasTodo() is false"; return; } IncidenceEditorNG::IncidenceDefaults defaults = IncidenceEditorNG::IncidenceDefaults::minimalIncidenceDefaults(); defaults.setRelatedIncidence( CalendarSupport::incidence( selectedTodo() ) ); KCalCore::Todo::Ptr todo( new KCalCore::Todo ); defaults.setDefaults( todo ); Akonadi::Item item; item.setPayload( todo ); IncidenceEditorNG::IncidenceDialog *dialog = createIncidenceEditor( item, collection ); dialog->load( item ); } void CalendarView::newSubTodo( const Akonadi::Item &parentTodo ) { IncidenceEditorNG::IncidenceDefaults defaults = IncidenceEditorNG::IncidenceDefaults::minimalIncidenceDefaults(); defaults.setRelatedIncidence( CalendarSupport::incidence( parentTodo ) ); KCalCore::Todo::Ptr todo( new KCalCore::Todo ); defaults.setDefaults( todo ); Q_ASSERT( !todo->relatedTo().isEmpty() ); Akonadi::Item item; item.setPayload( todo ); // Don't use parentTodo.parentCollection() because that can be a search folder. Akonadi::Collection collection = mCalendar->collection( parentTodo.storageCollectionId() ); IncidenceEditorNG::IncidenceDialog *dialog = createIncidenceEditor( item, collection ); dialog->load( item ); } void CalendarView::newFloatingEvent() { const QDate date = activeDate(); newEvent( QDateTime( date, QTime( 12, 0, 0 ) ), QDateTime( date, QTime( 12, 0, 0 ) ), true ); } bool CalendarView::addIncidence( const QString &ical ) { KCalCore::ICalFormat format; format.setTimeSpec( mCalendar->timeSpec() ); KCalCore::Incidence::Ptr incidence( format.fromString( ical ) ); return addIncidence( incidence ); } bool CalendarView::addIncidence( const KCalCore::Incidence::Ptr &incidence ) { return incidence ? mChanger->createIncidence( incidence, Akonadi::Collection(), this ) != -1 : false; } void CalendarView::appointment_show() { const Akonadi::Item item = selectedIncidence(); if ( CalendarSupport::hasIncidence( item ) ) { showIncidence( item ); } else { KNotification::beep(); } } void CalendarView::appointment_edit() { const Akonadi::Item item = selectedIncidence(); if ( CalendarSupport::hasIncidence( item ) ) { editIncidence( item ); } else { KNotification::beep(); } } void CalendarView::appointment_delete() { const Akonadi::Item item = selectedIncidence(); if ( CalendarSupport::hasIncidence( item ) ) { deleteIncidence( item ); } else { KNotification::beep(); } } void CalendarView::todo_unsub() { const Akonadi::Item aTodo = selectedTodo(); if ( incidence_unsub( aTodo ) ) { updateView(); } } bool CalendarView::incidence_unsub( const Akonadi::Item &item ) { const KCalCore::Incidence::Ptr inc = CalendarSupport::incidence( item ); if ( !inc || inc->relatedTo().isEmpty() ) { kDebug() << "Refusing to unparent this to-do" << inc; return false; } KCalCore::Incidence::Ptr oldInc( inc->clone() ); inc->setRelatedTo( QString() ); mChanger->modifyIncidence( item, oldInc, this ); return true; } bool CalendarView::makeSubTodosIndependent( ) { bool status = false; const Akonadi::Item aTodo = selectedTodo(); if( makeChildrenIndependent( aTodo ) ) { updateView(); status = true; } return status; } bool CalendarView::makeChildrenIndependent( const Akonadi::Item &item ) { const KCalCore::Incidence::Ptr inc = CalendarSupport::incidence( item ); Akonadi::Item::List subIncs = mCalendar->childItems( item.id() ); if ( !inc || subIncs.isEmpty() ) { kDebug() << "Refusing to make children independent" << inc; return false; } startMultiModify ( i18n( "Make sub-to-dos independent" ) ); foreach ( const Akonadi::Item &item, subIncs ) { incidence_unsub( item ); } endMultiModify(); return true; } bool CalendarView::deleteIncidence( Akonadi::Item::Id id, bool force ) { Akonadi::Item item = mCalendar->item( id ); if ( !CalendarSupport::hasIncidence( item ) ) { kError() << "CalendarView::deleteIncidence(): Item does not contain incidence."; return false; } return deleteIncidence( item, force ); } void CalendarView::toggleAlarm( const Akonadi::Item &item ) { const KCalCore::Incidence::Ptr incidence = CalendarSupport::incidence( item ); if ( !incidence ) { kError() << "Null incidence"; return; } KCalCore::Incidence::Ptr oldincidence( incidence->clone() ); KCalCore::Alarm::List alarms = incidence->alarms(); KCalCore::Alarm::List::ConstIterator it; for ( it = alarms.constBegin(); it != alarms.constEnd(); ++it ) { (*it)->toggleAlarm(); } if ( alarms.isEmpty() ) { // Add an alarm if it didn't have one KCalCore::Alarm::Ptr alm = incidence->newAlarm(); alm->setType( KCalCore::Alarm::Display ); alm->setEnabled( true ); int duration; // in secs switch( CalendarSupport::KCalPrefs::instance()->mReminderTimeUnits ) { default: case 0: // mins duration = CalendarSupport::KCalPrefs::instance()->mReminderTime * 60; break; case 1: // hours duration = CalendarSupport::KCalPrefs::instance()->mReminderTime * 60 * 60; break; case 2: // days duration = CalendarSupport::KCalPrefs::instance()->mReminderTime * 60 * 60 * 24; break; } if ( incidence->type() == KCalCore::Incidence::TypeEvent ) { alm->setStartOffset( KCalCore::Duration( -duration ) ); } else { alm->setEndOffset( KCalCore::Duration( -duration ) ); } } mChanger->startAtomicOperation( i18n( "Toggle Reminder" ) ); mChanger->modifyIncidence( item, oldincidence, this ); mChanger->endAtomicOperation(); } void CalendarView::toggleTodoCompleted( const Akonadi::Item &todoItem ) { const KCalCore::Incidence::Ptr incidence = CalendarSupport::incidence( todoItem ); if ( !incidence ) { kError() << "Null incidence"; return; } if ( incidence->type() != KCalCore::Incidence::TypeTodo ) { kDebug() << "called for a non-Todo incidence"; return; } KCalCore::Todo::Ptr todo = CalendarSupport::todo( todoItem ); Q_ASSERT( todo ); KCalCore::Todo::Ptr oldtodo( todo->clone() ); if ( todo->isCompleted() ) { todo->setPercentComplete( 0 ); } else { todo->setCompleted( KDateTime::currentDateTime( CalendarSupport::KCalPrefs::instance()->timeSpec() ) ); } mChanger->startAtomicOperation( i18n( "Toggle To-do Completed") ); mChanger->modifyIncidence( todoItem, oldtodo, this ); mChanger->endAtomicOperation(); } void CalendarView::copyIncidenceToResource( const Akonadi::Item &item, const QString &resourceId ) { #ifdef AKONADI_PORT_DISABLED if ( !incidence ) { kError() << "Null incidence"; return; } KCalCore::CalendarResources *const resources = KOrg::StdCalendar::self(); KCalCore::CalendarResourceManager *const manager = resources->resourceManager(); // Find the resource the incidence should be copied to ResourceCalendar *newCal = 0; KCalCore::CalendarResourceManager::iterator it; for ( it = manager->begin(); it != manager->end(); ++it ) { ResourceCalendar *const resource = *it; if ( resource->identifier() == resourceId ) { newCal = resource; break; } } if ( !newCal ) { return; } // Clone a new Incidence from the selected Incidence and give it a new Uid. KCalCore::Incidence::Ptr newInc; if ( incidence->type() == KCalCore::Incidence::TypeEvent ) { KCalCore::Event::Ptr nEvent( static_cast( incidence )->clone() ); nEvent->setUid( KCalCore::CalFormat::createUniqueId() ); newInc = nEvent; } else if ( incidence->type() == KCalCore::Incidence::TypeTodo ) { KCalCore::Todo::Ptr nTodo( static_cast( incidence )->clone() ); nTodo->setUid( KCalCore::CalFormat::createUniqueId() ); newInc = nTodo; } else if ( incidence->type() == KCalCore::Incidence::TypeJournal ) { KCalCore::Journal::Ptr nJournal( static_cast( incidence )->clone() ); nJournal->setUid( KCalCore::CalFormat::createUniqueId() ); newInc = nJournal; } else { kWarning() << "Trying to copy an incidence type that cannot be copied"; return; } if ( resources->addIncidence( newInc, newCal ) ) { incidenceAddFinished( newInc, true ); KMessageBox::information( this, i18nc( "@info", "\"%1\" was successfully copied to %2.", incidence->summary(), newCal->resourceName() ), i18nc( "@title:window", "Copying Succeeded" ), "CalendarIncidenceCopy" ); } else { KMessageBox::error( this, i18nc( "@info", "Unable to copy the item \"%1\" to %2.", incidence->summary(), newCal->resourceName() ), i18nc( "@title:window", "Copying Failed" ) ); } #else Q_UNUSED( resourceId ); Q_UNUSED( item ); kDebug() << "AKONADI PORT: Disabled code in " << Q_FUNC_INFO; #endif } void CalendarView::moveIncidenceToResource( const Akonadi::Item &item, const QString &resourceId ) { #ifdef AKONADI_PORT_DISABLED if ( !incidence ) { kError() << "Null incidence"; return; } KCalCore::CalendarResources *const resources = KOrg::StdCalendar::self(); KCalCore::CalendarResourceManager *const manager = resources->resourceManager(); // Find the resource the incidence should be moved to ResourceCalendar *newCal = 0; KCalCore::CalendarResourceManager::iterator it; for ( it = manager->begin(); it != manager->end(); ++it ) { ResourceCalendar *const resource = *it; if ( resource->identifier() == resourceId ) { newCal = resource; break; } } if ( !newCal ) { return; } // Clone a new Incidence from the selected Incidence and give it a new Uid. KCalCore::Incidence *newInc; if ( incidence->type() == KCalCore::Incidence::TypeEvent ) { KCalCore::Event::Ptr nEvent = static_cast( incidence )->clone(); nEvent->setUid( KCalCore::CalFormat::createUniqueId() ); newInc = nEvent; } else if ( incidence->type() == KCalCore::Incidence::TypeTodo ) { KCalCore::Todo::Ptr nTodo = static_cast( incidence )->clone(); nTodo->setUid( KCalCore::CalFormat::createUniqueId() ); newInc = nTodo; } else if ( incidence->type() == KCalCore::Incidence::TypeJournal ) { KCalCore::Journal::Ptr nJournal = static_cast( incidence )->clone(); nJournal->setUid( KCalCore::CalFormat::createUniqueId() ); newInc = nJournal; } else { kWarning() << "Trying to move an incidence type that cannot be moved"; return; } if ( resources->addIncidence( newInc, newCal ) ) { incidenceAddFinished( newInc, true ); ResourceCalendar *const oldCal = resources->resource( incidence ); if ( !oldCal || resources->deleteIncidence( incidence ) ) { KMessageBox::error( this, i18nc( "@info", "Unable to remove the item \"%1\" from %2. " "However, a copy of this item has been put into %3.", incidence->summary(), oldCal->resourceName(), newCal->resourceName() ), i18nc( "@title:window", "Moving Failed" ) ); } else { incidenceDeleteFinished( incidence, true ); KMessageBox::information( this, i18nc( "@info", "\"%1\" was successfully moved from %2 to %3.", incidence->summary(), oldCal->resourceName(), newCal->resourceName() ), i18nc( "@title:window", "Moving Succeeded" ), "CalendarIncidenceMove" ); } } else { KMessageBox::error( this, i18nc( "@info", "Unable to add the item \"%1\" into %2. " "This item has not been moved.", incidence->summary(), newCal->resourceName() ), i18nc( "@title:window", "Moving Failed" ) ); } #else Q_UNUSED( resourceId ); Q_UNUSED( item ); kDebug() << "AKONADI PORT: Disabled code in " << Q_FUNC_INFO; #endif } void CalendarView::dissociateOccurrences( const Akonadi::Item &item, const QDate &date ) { const KCalCore::Incidence::Ptr incidence = CalendarSupport::incidence( item ); if ( !incidence ) { kError() << "Null incidence"; return; } KDateTime thisDateTime( date, CalendarSupport::KCalPrefs::instance()->timeSpec() ); bool isFirstOccurrence = !incidence->recurrence()->getPreviousDateTime( thisDateTime ).isValid(); int answer; bool doOnlyThis = false; bool doFuture = false; if ( isFirstOccurrence ) { answer = KMessageBox::questionYesNo( this, i18n( "Do you want to dissociate " "the occurrence on %1 " "from the recurrence?", KGlobal::locale()->formatDate( date ) ), i18n( "KOrganizer Confirmation" ), KGuiItem( i18n( "&Dissociate" ) ), KStandardGuiItem::cancel() ); doOnlyThis = ( answer == KMessageBox::Yes ); } else { answer = KMessageBox::questionYesNoCancel( this, i18n( "Do you want to dissociate " "the occurrence on %1 " "from the recurrence or also " "dissociate future ones?", KGlobal::locale()->formatDate( date ) ), i18n( "KOrganizer Confirmation" ), KGuiItem( i18n( "&Only Dissociate This One" ) ), KGuiItem( i18n( "&Also Dissociate Future Ones" ) ) ); doOnlyThis = ( answer == KMessageBox::Yes ); doFuture = ( answer == KMessageBox::No ); } if ( doOnlyThis ) { dissociateOccurrence( item, date, false ); } else if ( doFuture ) { dissociateOccurrence( item, date, true ); } } void CalendarView::dissociateOccurrence( const Akonadi::Item &item, const QDate &date, bool thisAndFuture ) { const KCalCore::Incidence::Ptr incidence = CalendarSupport::incidence( item ); if ( thisAndFuture ) { startMultiModify( i18n( "Dissociate future occurrences" ) ); } else { startMultiModify( i18n( "Dissociate occurrence" ) ); } KDateTime occurrenceDate( incidence->dtStart() ); occurrenceDate.setDate(date); kDebug() << "create exception: " << occurrenceDate; KCalCore::Incidence::Ptr newInc( KCalCore::Calendar::createException( incidence, occurrenceDate, thisAndFuture) ); if ( newInc ) { mChanger->createIncidence( newInc, item.parentCollection(), this ); } else { if ( thisAndFuture ) { KMessageBox::sorry( this, i18n( "Dissociating the future occurrences failed." ), i18n( "Dissociating Failed" ) ); } else { KMessageBox::sorry( this, i18n( "Dissociating the occurrence failed." ), i18n( "Dissociating Failed" ) ); } } endMultiModify(); } void CalendarView::schedule_publish( const Akonadi::Item &item ) { Akonadi::Item selectedItem = item; if ( !item.hasPayload() ) { selectedItem = selectedIncidence(); } KCalCore::Incidence::Ptr incidence = CalendarSupport::incidence( selectedItem ); if ( incidence ) mITIPHandler->publishInformation( incidence, this ); } void CalendarView::schedule_request( const Akonadi::Item &incidence ) { schedule( KCalCore::iTIPRequest, incidence ); } void CalendarView::schedule_refresh( const Akonadi::Item &incidence ) { schedule( KCalCore::iTIPRefresh, incidence ); } void CalendarView::schedule_cancel( const Akonadi::Item &incidence ) { schedule( KCalCore::iTIPCancel, incidence ); } void CalendarView::schedule_add( const Akonadi::Item &incidence ) { schedule( KCalCore::iTIPAdd, incidence ); } void CalendarView::schedule_reply( const Akonadi::Item &incidence ) { schedule( KCalCore::iTIPReply, incidence ); } void CalendarView::schedule_counter( const Akonadi::Item &incidence ) { schedule( KCalCore::iTIPCounter, incidence ); } void CalendarView::schedule_declinecounter( const Akonadi::Item &incidence ) { schedule( KCalCore::iTIPDeclineCounter, incidence ); } void CalendarView::schedule_forward( const Akonadi::Item &item ) { Akonadi::Item selectedItem = item; if ( !item.hasPayload() ) { selectedItem = selectedIncidence(); } KCalCore::Incidence::Ptr incidence = CalendarSupport::incidence( selectedItem ); if ( incidence ) mITIPHandler->sendAsICalendar( incidence, this ); } void CalendarView::mailFreeBusy( int daysToPublish ) { Akonadi::FreeBusyManager::self()->mailFreeBusy( daysToPublish, this ); } void CalendarView::uploadFreeBusy() { Akonadi::FreeBusyManager::self()->publishFreeBusy( this ); } void CalendarView::schedule( KCalCore::iTIPMethod method, const Akonadi::Item &item ) { Akonadi::Item selectedItem = item; if ( !item.hasPayload() ) { selectedItem = selectedIncidence(); } KCalCore::Incidence::Ptr incidence = CalendarSupport::incidence( selectedItem ); if ( incidence ) mITIPHandler->sendiTIPMessage( method, incidence, this ); } void CalendarView::openAddressbook() { KRun::runCommand( QLatin1String("kaddressbook"), topLevelWidget() ); } bool CalendarView::isReadOnly() const { return mReadOnly; } void CalendarView::setReadOnly( bool readOnly ) { if ( mReadOnly != readOnly ) { mReadOnly = readOnly; emit readOnlyChanged( mReadOnly ); } } void CalendarView::print() { createPrinter(); KOrg::BaseView *currentView = mViewManager->currentView(); CalendarSupport::CalPrinter::PrintType printType = CalendarSupport::CalPrinter::Month; KCalCore::Incidence::List selectedIncidences; if ( currentView ) { printType = currentView->printType(); Akonadi::Item::List selectedViewIncidences = currentView->selectedIncidences(); foreach ( const Akonadi::Item &item, selectedViewIncidences ) { if ( item.hasPayload() ) { selectedIncidences.append( item.payload() ); } } } KCalCore::DateList tmpDateList = mDateNavigator->selectedDates(); mCalPrinter->print( printType, tmpDateList.first(), tmpDateList.last(), selectedIncidences ); } void CalendarView::printPreview() { createPrinter(); KOrg::BaseView *currentView = mViewManager->currentView(); CalendarSupport::CalPrinter::PrintType printType = CalendarSupport::CalPrinter::Month; KCalCore::Incidence::List selectedIncidences; if ( currentView ) { printType = currentView->printType(); Akonadi::Item::List selectedViewIncidences = currentView->selectedIncidences(); foreach ( const Akonadi::Item &item, selectedViewIncidences ) { if ( item.hasPayload() ) { selectedIncidences.append( item.payload() ); } } } KCalCore::DateList tmpDateList = mDateNavigator->selectedDates(); mCalPrinter->print( printType, tmpDateList.first(), tmpDateList.last(), selectedIncidences, true ); } void CalendarView::exportWeb() { KOrg::HTMLExportSettings *settings = new KOrg::HTMLExportSettings( QLatin1String("KOrganizer") ); Q_ASSERT(settings); // Manually read in the config, because parameterized kconfigxt objects don't // seem to load the config theirselves settings->readConfig(); ExportWebDialog *dlg = new ExportWebDialog( settings, this ); connect( dlg, SIGNAL(exportHTML(KOrg::HTMLExportSettings*)), this, SIGNAL(exportHTML(KOrg::HTMLExportSettings*)) ); dlg->show(); } void CalendarView::exportICalendar() { QString filename = KFileDialog::getSaveFileName( KUrl( QLatin1String("icalout.ics") ), i18n( "*.ics|iCalendars" ), this ); if ( !filename.isEmpty() ) { // Force correct extension if ( filename.right( 4 ) != QLatin1String(".ics") ) { filename += QLatin1String(".ics"); } if ( QFile( filename ).exists() ) { if ( KMessageBox::No == KMessageBox::warningYesNo( this, i18n( "Do you want to overwrite %1?", filename ) ) ) { return; } } KCalCore::ICalFormat *format = new KCalCore::ICalFormat; KCalCore::FileStorage storage( mCalendar, filename, format ); if ( !storage.save() ) { QString errmess; if ( format->exception() ) { errmess = KCalUtils::Stringify::errorMessage( *format->exception() ); } else { errmess = i18nc( "save failure cause unknown", "Reason unknown" ); } KMessageBox::error( this, i18nc( "@info", "Cannot write iCalendar file %1. %2", filename, errmess ) ); } } } void CalendarView::exportVCalendar() { if ( !mCalendar->journals().isEmpty() ) { int result = KMessageBox::warningContinueCancel( this, i18n( "The journal entries cannot be exported to a vCalendar file." ), i18n( "Data Loss Warning" ), KGuiItem( i18n( "Proceed" ) ), KStandardGuiItem::cancel(), QLatin1String( "dontaskVCalExport" ), KMessageBox::Notify ); if ( result != KMessageBox::Continue ) { return; } } QString filename = KFileDialog::getSaveFileName( KUrl( QLatin1String("vcalout.vcs") ), i18n( "*.vcs|vCalendars" ), this ); if ( !filename.isEmpty() ) { // Force correct extension if ( filename.right( 4 ) != QLatin1String(".vcs") ) { filename += QLatin1String(".vcs"); } if ( QFile( filename ).exists() ) { if ( KMessageBox::No == KMessageBox::warningYesNo( this, i18n( "Do you want to overwrite %1?", filename ) ) ) { return; } } KCalCore::VCalFormat *format = new KCalCore::VCalFormat; KCalCore::FileStorage storage( mCalendar, filename, format ); if ( !storage.save() ) { QString errmess; if ( format->exception() ) { errmess = KCalUtils::Stringify::errorMessage( *format->exception() ); } else { errmess = i18nc( "save failure cause unknown", "Reason unknown" ); } KMessageBox::error( this, i18nc( "@info", "Cannot write vCalendar file %1. %2", filename, errmess ) ); } } } void CalendarView::eventUpdated( const Akonadi::Item & ) { // Don't call updateView here. The code, which has caused the update of the // event is responsible for updating the view. // updateView(); } void CalendarView::adaptNavigationUnits() { if ( mViewManager->currentView()->isEventView() ) { int days = mViewManager->currentView()->currentDateCount(); if ( days == 1 ) { emit changeNavStringPrev( i18n( "&Previous Day" ) ); emit changeNavStringNext( i18n( "&Next Day" ) ); } else { emit changeNavStringPrev( i18n( "&Previous Week" ) ); emit changeNavStringNext( i18n( "&Next Week" ) ); } } } void CalendarView::processMainViewSelection( const Akonadi::Item &item, const QDate &date ) { if ( CalendarSupport::hasIncidence( item ) ) { mTodoList->clearSelection(); } processIncidenceSelection( item, date ); } void CalendarView::processTodoListSelection( const Akonadi::Item &item, const QDate &date ) { if ( CalendarSupport::hasIncidence( item ) && mViewManager->currentView() ) { mViewManager->currentView()->clearSelection(); } processIncidenceSelection( item, date ); } void CalendarView::processIncidenceSelection( const Akonadi::Item &item, const QDate &date ) { KCalCore::Incidence::Ptr incidence = CalendarSupport::incidence( item ); if ( item != mSelectedIncidence ) { // This signal also must be emitted if incidence is 0 emit incidenceSelected( item, date ); } if ( !incidence ) { mSelectedIncidence = item; return; } if ( item == mSelectedIncidence ) { if ( !incidence->recurs() || mSaveDate == date ) { return; } } mSelectedIncidence = item; mSaveDate = date; bool todo = false; bool subtodo = false; const bool organizerEvents = CalendarSupport::KCalPrefs::instance()->thatIsMe( incidence->organizer()->email() ); const bool groupEvents = incidence->attendeeByMails( CalendarSupport::KCalPrefs::instance()->allEmails() ); if ( incidence->type() == KCalCore::Incidence::TypeTodo ) { todo = true; subtodo = ( incidence->relatedTo() != QString() ); } emit todoSelected( todo ); emit subtodoSelected( subtodo ); emit organizerEventsSelected( organizerEvents ); emit groupEventsSelected( groupEvents ); } void CalendarView::checkClipboard() { emit pasteEnabled( mCalendarClipboard->pasteAvailable() ); } void CalendarView::showDates( const KCalCore::DateList &selectedDates, const QDate &preferredMonth ) { mDateNavigatorContainer->selectDates( selectedDates, preferredMonth ); mNavigatorBar->selectDates( selectedDates ); if ( mViewManager->currentView() ) { updateView( selectedDates.first(), selectedDates.last(), preferredMonth, false ); } else { mViewManager->showAgendaView(); } } void CalendarView::editFilters() { mDialogManager->showFilterEditDialog( &mFilters ); } void CalendarView::updateFilter() { QStringList filters; int pos = mFilters.indexOf( mCurrentFilter ); if ( pos < 0 ) { mCurrentFilter = 0; } filters << i18n( "No filter" ); foreach ( KCalCore::CalFilter *filter, mFilters ) { if ( filter ) { filters << filter->name(); } } // account for the additional "No filter" at the beginning! if the // filter is not in the list, pos == -1... emit filtersUpdated( filters, pos + 1 ); mCalendar->setFilter( mCurrentFilter ); } void CalendarView::filterActivated( int filterNo ) { KCalCore::CalFilter *newFilter = 0; if ( filterNo > 0 && filterNo <= int( mFilters.count() ) ) { newFilter = mFilters.at( filterNo - 1 ); } if ( newFilter != mCurrentFilter ) { mCurrentFilter = newFilter; mCalendar->setFilter( mCurrentFilter ); mViewManager->addChange( EventViews::EventView::FilterChanged ); updateView(); } emit filterChanged(); } bool CalendarView::isFiltered() const { return mCurrentFilter != 0; } QString CalendarView::currentFilterName() const { if ( mCurrentFilter ) { return mCurrentFilter->name(); } else { return i18n( "No filter" ); } } void CalendarView::takeOverEvent() { const Akonadi::Item item = currentSelection(); KCalCore::Incidence::Ptr incidence = CalendarSupport::incidence( item ); if ( incidence ) { return; } incidence->setOrganizer( KCalCore::Person::Ptr( new KCalCore::Person( CalendarSupport::KCalPrefs::instance()->fullName(), CalendarSupport::KCalPrefs::instance()->email() ) ) ); incidence->recreate(); incidence->setReadOnly( false ); //PENDING(AKONADI_PORT) call mChanger? updateView(); } void CalendarView::showIntro() { kDebug() << "To be implemented."; } void CalendarView::showDateNavigator( bool show ) { if ( show ) { mDateNavigatorContainer->show(); mDateNavigatorContainer->updateView(); } else { mDateNavigatorContainer->hide(); } } void CalendarView::showTodoView( bool show ) { if ( show ) { mTodoList->show(); mTodoList->updateView(); } else { mTodoList->hide(); } } void CalendarView::showEventViewer( bool show ) { if ( show ) { mEventViewerBox->show(); } else { mEventViewerBox->hide(); } } void CalendarView::addView( KOrg::BaseView *view ) { mViewManager->addView( view ); } void CalendarView::showView( KOrg::BaseView *view ) { mViewManager->showView( view ); } void CalendarView::addExtension( CalendarViewExtension::Factory *factory ) { CalendarViewExtension *extension = factory->create( mLeftSplitter ); mExtensions.append( extension ); if ( !mETMCollectionView ) { mETMCollectionView = qobject_cast( extension ); } } void CalendarView::showLeftFrame( bool show ) { if ( show ) { mMainSplitterSizes.clear(); mLeftFrame->show(); emit calendarViewExpanded( false ); } else { // mPanner splitter sizes are useless if mLeftFrame is hidden, so remember them beforehand. if ( mMainSplitterSizes.isEmpty() ) { mMainSplitterSizes = mPanner->sizes(); } mLeftFrame->hide(); emit calendarViewExpanded( true ); } } Akonadi::Item CalendarView::selectedTodo() { const Akonadi::Item item = currentSelection(); if ( const KCalCore::Todo::Ptr t = CalendarSupport::todo( item ) ) { return item; } Akonadi::Item incidence; const Akonadi::Item::List selectedIncidences = mTodoList->selectedIncidences(); if ( !selectedIncidences.isEmpty() ) { incidence = selectedIncidences.first(); } if ( const KCalCore::Todo::Ptr t = CalendarSupport::todo( item ) ) { return item; } return Akonadi::Item(); } void CalendarView::dialogClosing( const Akonadi::Item & ) { } Akonadi::Item CalendarView::currentSelection() { return mViewManager->currentSelection(); } Akonadi::Item CalendarView::selectedIncidence() { Akonadi::Item item = currentSelection(); if ( !item.isValid() ) { Akonadi::Item::List selectedIncidences = mTodoList->selectedIncidences(); if ( !selectedIncidences.isEmpty() ) { item = selectedIncidences.first(); } } return item; } void CalendarView::showIncidence() { showIncidence( selectedIncidence() ); } void CalendarView::editIncidence() { editIncidence( selectedIncidence() ); } bool CalendarView::editIncidence( Akonadi::Item::Id id ) { Akonadi::Item item = mCalendar->item( id ); return editIncidence( item ); } bool CalendarView::showIncidence( Akonadi::Item::Id id ) { Akonadi::Item item = mCalendar->item( id ); if ( !CalendarSupport::hasIncidence( item ) ) { return false; } return true; } bool CalendarView::showIncidenceContext( Akonadi::Item::Id id ) { Akonadi::Item item = mCalendar->item( id ); if ( !CalendarSupport::hasIncidence( item ) ) { return false; } showIncidenceContext( item ); return true; } void CalendarView::deleteIncidence() { deleteIncidence( selectedIncidence() ); } void CalendarView::cutIncidence( const Akonadi::Item &incidence ) { Q_UNUSED( incidence ); edit_cut(); } void CalendarView::copyIncidence( const Akonadi::Item &incidence ) { Q_UNUSED( incidence ); edit_copy(); } void CalendarView::pasteIncidence() { edit_paste(); } void CalendarView::showIncidence( const Akonadi::Item &item ) { KOEventViewerDialog *eventViewer = new KOEventViewerDialog( mCalendar.data(), this ); eventViewer->setIncidence( item, QDate() ); // Disable the Edit button for read-only Incidences. if ( !mCalendar->hasRight( item, Akonadi::Collection::CanChangeItem ) ) { eventViewer->enableButton( KDialog::User1, false ); } eventViewer->show(); } void CalendarView::showIncidenceContext( const Akonadi::Item &item ) { KCalCore::Incidence::Ptr incidence = CalendarSupport::incidence( item ); if ( CalendarSupport::hasEvent( item ) ) { if ( !viewManager()->currentView()->inherits( "KOEventView" ) ) { viewManager()->showAgendaView(); } // just select the appropriate date mDateNavigator->selectWeek( incidence->dtStart().toTimeSpec( CalendarSupport::KCalPrefs::instance()->timeSpec() ).date() ); return; } else if ( CalendarSupport::hasJournal( item ) ) { if ( !viewManager()->currentView()->inherits( "KOJournalView" ) ) { viewManager()->showJournalView(); } } else if ( CalendarSupport::hasTodo( item ) ) { if ( !viewManager()->currentView()->inherits( "KOTodoView" ) ) { viewManager()->showTodoView(); } } Akonadi::Item::List list; list.append( item ); viewManager()->currentView()->showIncidences( list, QDate() ); } bool CalendarView::editIncidence( const Akonadi::Item &item, const KDateTime &occurrenceDateTime, bool isCounter ) { Q_UNUSED( isCounter ); KCalCore::Incidence::Ptr incidence = CalendarSupport::incidence( item ); if ( !incidence ) { kError() << "Null incidence"; KNotification::beep(); return false; } if ( !mCalendar->hasRight( item, Akonadi::Collection::CanChangeItem ) ) { showIncidence( item ); return true; } IncidenceEditorNG::IncidenceDialog *dialog = incidenceDialog( item ); // connectIncidenceEditor( dialog ); // TODO: This as well dialog->load( item, activeIncidenceDate(), occurrenceDateTime ); // Show the dialog as soon as it loads the item. return true; } void CalendarView::deleteSubTodosIncidence ( const Akonadi::Item &todoItem ) { const KCalCore::Todo::Ptr todo = CalendarSupport::todo( todoItem ); if ( !todo ) { return; } Akonadi::Item::List subTodos = mCalendar->childItems( todoItem.id() ); foreach ( const Akonadi::Item &item, subTodos ) { if ( CalendarSupport::hasTodo( item ) ) { deleteSubTodosIncidence ( item ); } } if ( !mChanger->deletedRecently( todoItem.id() ) ) { mChanger->deleteIncidence( todoItem, this ); } } void CalendarView::deleteTodoIncidence ( const Akonadi::Item &todoItem, bool force ) { const KCalCore::Todo::Ptr todo = CalendarSupport::todo( todoItem ); if ( !todo ) { return ; } // it a simple todo, ask and delete it. if ( mCalendar->childItems( todoItem.id() ).isEmpty() ) { bool doDelete = true; if ( !force && KOPrefs::instance()->mConfirm ) { doDelete = ( msgItemDelete( todoItem ) == KMessageBox::Continue ); } if ( doDelete && !mChanger->deletedRecently( todoItem.id() ) ) { mChanger->deleteIncidence( todoItem, this ); } return; } /* Ok, this to-do has sub-to-dos, ask what to do */ int km = KMessageBox::No; if ( !force ) { km = KMessageBox::questionYesNoCancel( this, i18n( "The item \"%1\" has sub-to-dos. " "Do you want to delete just this item and " "make all its sub-to-dos independent, or " "delete the to-do with all its sub-to-dos?", todo->summary() ), i18n( "KOrganizer Confirmation" ), KGuiItem( i18n( "Delete Only This" ) ), KGuiItem( i18n( "Delete All" ) ) ); } // Delete only the father if ( km == KMessageBox::Yes ) { startMultiModify( i18n( "Delete parent to-do" ) ); makeChildrenIndependent( todoItem ); if ( !mChanger->deletedRecently( todoItem.id() ) ) { mChanger->deleteIncidence( todoItem, this ); } } else if ( km == KMessageBox::No ) { startMultiModify( i18n( "Delete parent to-do and sub-to-dos" ) ); // Delete all // we have to hide the delete confirmation for each itemDate deleteSubTodosIncidence ( todoItem ); } endMultiModify(); } bool CalendarView::deleteIncidence( const Akonadi::Item &item, bool force ) { KCalCore::Incidence::Ptr incidence = CalendarSupport::incidence( item ); if ( !incidence ) { if ( !force ) { kError() << "Null incidence"; KNotification::beep(); } kError() << "CalendarView::deleteIncidence(): Unable do delete, incidence is null."; return false; } if ( mChanger->deletedRecently( item.id() ) ) { // it was deleted already but the etm wasn't notified yet kWarning() << "CalendarView::deleteIncidence(): item with id" << item.id() << "was deleted recently, skipping"; return true; } if ( !mCalendar->hasRight( item, Akonadi::Collection::CanDeleteItem ) ) { if ( !force ) { KMessageBox::information( this, i18n( "The item \"%1\" is marked read-only " "and cannot be deleted; it probably " "belongs to a read-only calendar.", incidence->summary() ), i18n( "Removing not possible" ), QLatin1String("deleteReadOnlyIncidence") ); } kWarning() << "CalendarView::deleteIncidence(): No rights to delete item"; return false; } //If it is a todo, there are specific delete function if ( incidence && incidence->type() == KCalCore::Incidence::TypeTodo ) { deleteTodoIncidence( item, force ); return true; } if ( incidence->recurs() ) { QDate itemDate = mViewManager->currentSelectionDate(); int km = KMessageBox::Ok; if ( !force ) { if ( !itemDate.isValid() ) { kDebug() << "Date Not Valid"; km = KMessageBox::warningContinueCancel( this, i18n( "The calendar item \"%1\" recurs over multiple dates; " "are you sure you want to delete it " "and all its recurrences?", incidence->summary() ), i18n( "KOrganizer Confirmation" ), KGuiItem( i18n( "Delete All" ) ) ); } else { KDateTime itemDateTime( itemDate, CalendarSupport::KCalPrefs::instance()->timeSpec() ); bool isFirst = !incidence->recurrence()->getPreviousDateTime( itemDateTime ).isValid(); bool isLast = !incidence->recurrence()->getNextDateTime( itemDateTime ).isValid(); QString message; KGuiItem itemFuture( i18n( "Also Delete &Future" ) ); if ( !isFirst && !isLast ) { itemFuture.setEnabled( true ); message = i18n( "The calendar item \"%1\" recurs over multiple dates. " "Do you want to delete only the current one on %2, also " "future occurrences, or all its occurrences?", incidence->summary(), KGlobal::locale()->formatDate( itemDate ) ); } else { itemFuture.setEnabled( false ); message = i18n( "The calendar item \"%1\" recurs over multiple dates. " "Do you want to delete only the current one on %2 " "or all its occurrences?", incidence->summary(), KGlobal::locale()->formatDate( itemDate ) ); } if ( !( isFirst && isLast ) ) { km = PIMMessageBox::fourBtnMsgBox( this, QMessageBox::Warning, message, i18n( "KOrganizer Confirmation" ), KGuiItem ( i18n( "Delete C&urrent" ) ), itemFuture, KGuiItem( i18n( "Delete &All" ) ) ); } else { km = msgItemDelete( item ); } } } KCalCore::Incidence::Ptr oldIncidence( incidence->clone() ); switch( km ) { case KMessageBox::Ok: // Continue // all case KMessageBox::Continue: mChanger->deleteIncidence( item, this ); break; case KMessageBox::Yes: // just this one incidence->recurrence()->addExDate( itemDate ); mChanger->modifyIncidence( item, oldIncidence, this ); break; case KMessageBox::No: // all future items KCalCore::Recurrence *recur = incidence->recurrence(); recur->setEndDate( itemDate.addDays( -1 ) ); mChanger->modifyIncidence( item, oldIncidence, this ); break; } } else { bool doDelete = true; if ( !force && KOPrefs::instance()->mConfirm ) { doDelete = ( msgItemDelete( item ) == KMessageBox::Continue ); } if ( doDelete ) { mChanger->deleteIncidence( item, this ); processIncidenceSelection( Akonadi::Item(), QDate() ); } } return true; } void CalendarView::purgeCompleted() { if ( checkedCollections().isEmpty() ) { showMessage( i18n( "All calendars are unchecked in the Calendar Manager. No to-do was purged." ), KMessageWidget::Warning ); return; } if ( mCalendar->rawTodos().isEmpty() ) { showMessage( i18n( "There are no completed to-dos to purge." ), KMessageWidget::Information ); return; } int result = KMessageBox::warningContinueCancel( this, i18n( "Delete all completed to-dos from checked calendars?" ), i18n( "Purge To-dos" ), KGuiItem( i18n( "Purge" ) ) ); if ( result == KMessageBox::Continue ) { mTodoPurger->purgeCompletedTodos(); } } void CalendarView::warningChangeFailed( const Akonadi::Item &item ) { KCalCore::Incidence::Ptr incidence = CalendarSupport::incidence( item ); if ( incidence ) { KMessageBox::sorry( this, i18nc( "@info", "Unable to edit \"%1\" because it is locked by another process.", incidence->summary() ) ); } } void CalendarView::showErrorMessage( const QString &msg ) { KMessageBox::error( this, msg ); } void CalendarView::addIncidenceOn( const Akonadi::Item &itemadd, const QDate &dt ) { if ( !CalendarSupport::hasIncidence( itemadd ) ) { KMessageBox::sorry( this, i18n( "Unable to copy the item to %1.", dt.toString() ), i18n( "Copying Failed" ) ); return; } Akonadi::Item item = mCalendar->item( itemadd.id() ); if ( !item.isValid() ) { item = itemadd; } // Create a copy of the incidence, since the incadd doesn't belong to us. KCalCore::Incidence::Ptr incidence( CalendarSupport::incidence( item )->clone() ); incidence->recreate(); if ( const KCalCore::Event::Ptr event = incidence.dynamicCast() ) { // Adjust date KDateTime start = event->dtStart(); KDateTime end = event->dtEnd(); int duration = start.daysTo( end ); start.setDate( dt ); end.setDate( dt.addDays( duration ) ); event->setDtStart( start ); event->setDtEnd( end ); } else if ( const KCalCore::Todo::Ptr todo = incidence.dynamicCast() ) { KDateTime due = todo->dtDue(); due.setDate( dt ); todo->setDtDue( due ); } mChanger->createIncidence( incidence, Akonadi::Collection(), this ); } void CalendarView::moveIncidenceTo( const Akonadi::Item &itemmove, const QDate &dt ) { if ( !CalendarSupport::hasIncidence( itemmove ) ) { KMessageBox::sorry( this, i18n( "Unable to move the item to %1.", dt.toString() ), i18n( "Moving Failed" ) ); return; } Akonadi::Item item = mCalendar->item( itemmove.id() ); if ( !item.isValid() ) { addIncidenceOn( itemmove, dt ); return; } KCalCore::Incidence::Ptr incidence = CalendarSupport::incidence( itemmove ); KCalCore::Incidence::Ptr oldIncidence( incidence->clone() ); if ( const KCalCore::Event::Ptr event = incidence.dynamicCast() ) { // Adjust date KDateTime start = event->dtStart(); KDateTime end = event->dtEnd(); int duration = start.daysTo( end ); start.setDate( dt ); end.setDate( dt.addDays( duration ) ); event->setDtStart( start ); event->setDtEnd( end ); } if ( const KCalCore::Todo::Ptr todo = incidence.dynamicCast() ) { KDateTime due = todo->dtDue(); due.setDate( dt ); todo->setDtDue( due ); } mChanger->modifyIncidence( itemmove, oldIncidence, this ); } void CalendarView::resourcesChanged() { mViewManager->addChange( EventViews::EventView::ResourcesChanged ); updateView(); } bool CalendarView::eventFilter( QObject *watched, QEvent *event ) { if ( watched == mLeftFrame && event->type() == QEvent::Show ) { mSplitterSizesValid = true; } return KOrg::CalendarViewBase::eventFilter( watched, event ); } void CalendarView::updateHighlightModes() { KOrg::BaseView *view = mViewManager->currentView(); if ( view ) { bool hiEvents; bool hiTodos; bool hiJournals; view->getHighlightMode( hiEvents, hiTodos, hiJournals ); mDateNavigatorContainer->setHighlightMode( hiEvents, hiTodos, hiJournals ); } } void CalendarView::selectWeek( const QDate &date, const QDate &preferredMonth ) { if ( KOPrefs::instance()->mWeekNumbersShowWork && mViewManager->rangeMode() == KOViewManager::WORK_WEEK_RANGE ) { mDateNavigator->selectWorkWeek( date ); } else { mDateNavigator->selectWeek( date, preferredMonth ); } } void CalendarView::changeFullView( bool fullView ) { if ( mViewManager->currentView() ) { if ( mViewManager->currentView()->identifier() == "DefaultTodoView" ) { showLeftFrame( !fullView ); } else if ( mViewManager->currentView()->identifier() == "DefaultMonthView" ) { showLeftFrame( !fullView ); fullView ? mNavigatorBar->show() : mNavigatorBar->hide(); } } } Akonadi::Collection CalendarView::defaultCollection( const QLatin1String &mimeType ) const { // 1. Try the view collection ( used in multi-agenda view ) Akonadi::Collection collection = mCalendar->collection( mViewManager->currentView()->collectionId() ); bool supportsMimeType = collection.contentMimeTypes().contains( mimeType ) || mimeType == ""; bool hasRights = collection.rights() & Akonadi::Collection::CanCreateItem; if ( collection.isValid() && supportsMimeType && hasRights ) return collection; // 2. Try the selected collection collection = selectedCollection(); supportsMimeType = collection.contentMimeTypes().contains( mimeType ) || mimeType == ""; hasRights = collection.rights() & Akonadi::Collection::CanCreateItem; if ( collection.isValid() && supportsMimeType && hasRights ) return collection; // 3. Try the checked collections Akonadi::Collection::List collections = checkedCollections(); foreach( const Akonadi::Collection &checkedCollection, collections ) { supportsMimeType = checkedCollection.contentMimeTypes().contains( mimeType ) || mimeType == ""; hasRights = checkedCollection.rights() & Akonadi::Collection::CanCreateItem; if ( checkedCollection.isValid() && supportsMimeType && hasRights ) return checkedCollection; } // 4. Try the configured default collection collection = mCalendar->collection( CalendarSupport::KCalPrefs::instance()->defaultCalendarId() ); supportsMimeType = collection.contentMimeTypes().contains( mimeType ) || mimeType == ""; hasRights = collection.rights() & Akonadi::Collection::CanCreateItem; if ( collection.isValid() && supportsMimeType && hasRights ) return collection; // 5. Return a invalid collection, the editor will use the first one in the combo return Akonadi::Collection(); } IncidenceEditorNG::IncidenceDialog *CalendarView::createIncidenceEditor( const Akonadi::Item &item, const Akonadi::Collection &collection ) { IncidenceEditorNG::IncidenceDialog *dialog = incidenceDialog( item ); KCalCore::Incidence::Ptr incidence = CalendarSupport::incidence( item ); Q_ASSERT( incidence ); if ( collection.isValid() ) { dialog->selectCollection( collection ); } else { dialog->selectCollection( defaultCollection( incidence->mimeType() ) ); } return dialog; } Akonadi::History *CalendarView::history() const { return mChanger->history(); } void CalendarView::onCutFinished() { checkClipboard(); } void CalendarView::setCheckableProxyModel( KOCheckableProxyModel *model ) { if ( mCheckableProxyModel ) mCheckableProxyModel->disconnect( this ); mCheckableProxyModel = model; connect( model, SIGNAL(aboutToToggle(bool)), SLOT(onCheckableProxyAboutToToggle(bool)) ); connect( model, SIGNAL(toggled(bool)), SLOT(onCheckableProxyToggled(bool)) ); } void CalendarView::onCheckableProxyAboutToToggle( bool newState ) { // Someone unchecked a collection, save the view state now. if ( !newState ) { mTodoList->saveViewState(); KOTodoView *todoView = mViewManager->todoView(); if ( todoView ) todoView->saveViewState(); } } void CalendarView::onCheckableProxyToggled( bool newState ) { // Someone checked a collection, restore the view state now. if ( newState ) { mTodoList->restoreViewState(); KOTodoView *todoView = mViewManager->todoView(); if ( todoView ) todoView->restoreViewState(); } } void CalendarView::onTodosPurged(bool success, int numDeleted, int numIgnored) { QString message; KMessageWidget::MessageType type = KMessageWidget::Information; if (success) { if (numDeleted == 0 && numIgnored > 0) { type = KMessageWidget::Warning; message = i18n("0 completed to-dos were purged.") + QLatin1Char('\n') + i18np("%1 to-do was ignored because it has uncompleted or read-only children.", "%1 to-dos were ignored because they have uncompleted or read-only children.", numIgnored); } else if (numDeleted > 0 && numIgnored == 0) { message = i18np("%1 completed to-do was purged.", "%1 completed to-dos were purged.", numDeleted); } else if (numDeleted == 0 && numIgnored == 0) { message = i18n("There are no completed to-dos to purge."); } else { type = KMessageWidget::Warning; message = i18np("%1 completed to-do was purged.", "%1 completed to-dos were purged.", numDeleted) + QLatin1Char('\n') + i18np("%1 to-do was ignored because it has uncompleted or read-only children.", "%1 to-dos were ignored because they have uncompleted or read-only children.", numIgnored); } } else { message = i18n("An error occurred while purging completed to-dos: %1", mTodoPurger->lastError()); type = KMessageWidget::Error; } showMessage(message, type); } void CalendarView::showMessage(const QString &message, KMessageWidget::MessageType type) { mMessageWidget->setText(message); mMessageWidget->setMessageType(type); mMessageWidget->show(); } Akonadi::Collection CalendarView::selectedCollection() const { return mETMCollectionView ? mETMCollectionView->selectedCollection() : Akonadi::Collection(); } Akonadi::Collection::List CalendarView::checkedCollections() const { Akonadi::Collection::List collections; if ( mETMCollectionView ) collections = mETMCollectionView->checkedCollections(); // If the default calendar is here, it should be first. int count = collections.count(); Akonadi::Collection::Id id = CalendarSupport::KCalPrefs::instance()->defaultCalendarId(); for( int i=0; iSidebar->Show Calendar Manager)"); } mMessageWidget->setText(message); mMessageWidget->setMessageType(KMessageWidget::Information); mMessageWidget->show(); } } diff --git a/korganizer/htmlexportjob.cpp b/korganizer/htmlexportjob.cpp index d68745baf9..b0b9f5583f 100644 --- a/korganizer/htmlexportjob.cpp +++ b/korganizer/htmlexportjob.cpp @@ -1,903 +1,894 @@ /* This file is part of the kcal library. Copyright (c) 2000,2001 Cornelius Schumacher Copyright (C) 2004 Reinhold Kainhofer This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "htmlexportjob.h" #include "htmlexportsettings.h" #include "korganizer/mainwindow.h" #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace KOrg; static QString cleanChars( const QString &txt ); //@cond PRIVATE class KOrg::HtmlExportJob::Private { public: Private( const Akonadi::ETMCalendar::Ptr &calendar, KOrg::HTMLExportSettings *settings, bool autoMode, KOrg::MainWindow *mainWindow, QWidget *parent ) : mCalendar( calendar ), mSettings( settings ), mAutoMode( autoMode ), mMainWindow( mainWindow ), mParentWidget( parent ), mSubJobCount( 0 ) {} Akonadi::ETMCalendar::Ptr mCalendar; KOrg::HTMLExportSettings *mSettings; bool mAutoMode; KOrg::MainWindow *mMainWindow; QWidget *mParentWidget; QMap mHolidayMap; qulonglong mSubJobCount; QMap mOrganizersMap; }; //@endcond HtmlExportJob::HtmlExportJob( const Akonadi::ETMCalendar::Ptr &calendar, KOrg::HTMLExportSettings *settings, bool autoMode, KOrg::MainWindow *mainWindow, QWidget *parent ) : KJob( parent ), d( new Private( calendar, settings, autoMode, mainWindow, parent ) ) { } HtmlExportJob::~HtmlExportJob() { //kDebug()<<"HtmlExportJob::~HtmlExportJob()"; delete d; } void HtmlExportJob::start() { bool canExportItem = false; // first collect the email addresses of all organisators const KCalCore::Event::List events = d->mCalendar->events(); foreach ( const KCalCore::Event::Ptr &event, events ) { Q_ASSERT( event ); const KCalCore::Attendee::List attendees = event->attendees(); if ( !attendees.isEmpty() ) { Akonadi::ContactSearchJob *job = new Akonadi::ContactSearchJob( this ); job->setQuery( Akonadi::ContactSearchJob::Email, event->organizer()->email() ); job->setProperty( "incidenceUid", event->uid() ); connect( job, SIGNAL(result(KJob*)), SLOT(receivedOrganizerInfo(KJob*)) ); job->start(); d->mSubJobCount++; canExportItem = true; } } const KCalCore::Todo::List todos = d->mCalendar->todos(); foreach ( const KCalCore::Todo::Ptr &todo, todos ) { Q_ASSERT( todo ); const KCalCore::Attendee::List attendees = todo->attendees(); if ( !attendees.isEmpty() ) { Akonadi::ContactSearchJob *job = new Akonadi::ContactSearchJob( this ); job->setQuery( Akonadi::ContactSearchJob::Email, todo->organizer()->email() ); job->setProperty( "incidenceUid", todo->uid() ); connect( job, SIGNAL(result(KJob*)), SLOT(receivedOrganizerInfo(KJob*)) ); job->start(); d->mSubJobCount++; canExportItem = true; } } if( !canExportItem ) { finishExport(); } } void HtmlExportJob::receivedOrganizerInfo( KJob *job ) { d->mSubJobCount--; if ( !job->error() ) { Akonadi::ContactSearchJob *searchJob = qobject_cast( job ); const KABC::Addressee::List contacts = searchJob->contacts(); if ( !contacts.isEmpty() ) { d->mOrganizersMap.insert( searchJob->property( "incidenceUid" ).toString(), contacts.first() ); } } if ( d->mSubJobCount == 0 ) { finishExport(); } } void HtmlExportJob::finishExport() { QApplication::setOverrideCursor( QCursor ( Qt::WaitCursor ) ); bool saveStatus; QString errorMessage; Q_ASSERT( d->mSettings ); KUrl dest( d->mSettings->outputFile() ); if ( dest.isLocalFile() ) { saveStatus = save( dest.toLocalFile() ); errorMessage = i18n( "Unable to write the output file." ); } else { KTemporaryFile tf; tf.open(); QString tfile = tf.fileName(); saveStatus = save( tfile ); errorMessage = i18n( "Unable to write the temporary file for uploading." ); if ( !KIO::NetAccess::upload( tfile, dest, d->mParentWidget ) ) { saveStatus = false; errorMessage = i18n( "Unable to upload the export file." ); } } QApplication::restoreOverrideCursor(); QString saveMessage; if ( saveStatus ) { saveMessage = i18n( "Web page successfully written to \"%1\"", dest.url() ); } else { saveMessage = i18n( "Export failed. %1", errorMessage ); } if ( !d->mAutoMode ) { KMessageBox::information( d->mParentWidget, saveMessage, i18nc( "@title:window", "Export Status" ) ); } else { d->mMainWindow->showStatusMessage( i18nc( "@info:status", "Automatic Export: %1", saveMessage ) ); } emitResult(); } bool HtmlExportJob::save( const QString &fileName ) { QString fn( fileName ); if ( fn.isEmpty() && d->mSettings ) { fn = d->mSettings->outputFile(); } if ( !d->mSettings || fn.isEmpty() ) { return false; } QFile f( fileName ); if ( !f.open( QIODevice::WriteOnly ) ) { return false; } QTextStream ts( &f ); bool success = save( &ts ); f.close(); return success; } bool HtmlExportJob::save( QTextStream *ts ) { if ( !d->mSettings ) { return false; } ts->setCodec( "UTF-8" ); // Write HTML header *ts << "" << endl; *ts << "" << endl; *ts << " " << endl; if ( !d->mSettings->pageTitle().isEmpty() ) { *ts << " " << d->mSettings->pageTitle() << "" << endl; } *ts << " " << endl; *ts << "" << endl; // FIXME: Write header // (Heading, Calendar-Owner, Calendar-Date, ...) if ( d->mSettings->eventView() || d->mSettings->monthView() || d->mSettings->weekView() ) { if ( !d->mSettings->eventTitle().isEmpty() ) { *ts << "

    " << d->mSettings->eventTitle() << "

    " << endl; } // Write Week View if ( d->mSettings->weekView() ) { createWeekView( ts ); } // Write Month View if ( d->mSettings->monthView() ) { createMonthView( ts ); } // Write Event List if ( d->mSettings->eventView() ) { createEventList( ts ); } } // Write Todo List if ( d->mSettings->todoView() ) { if ( !d->mSettings->todoListTitle().isEmpty() ) { *ts << "

    " << d->mSettings->todoListTitle() << "

    " << endl; } createTodoList( ts ); } // Write Journals if ( d->mSettings->journalView() ) { if ( !d->mSettings->journalTitle().isEmpty() ) { *ts << "

    " << d->mSettings->journalTitle() << "

    " << endl; } createJournalView( ts ); } // Write Free/Busy if ( d->mSettings->freeBusyView() ) { if ( !d->mSettings->freeBusyTitle().isEmpty() ) { *ts << "

    " << d->mSettings->freeBusyTitle() << "

    " << endl; } createFreeBusyView( ts ); } createFooter( ts ); // Write HTML trailer *ts << "" << endl; return true; } void HtmlExportJob::createMonthView( QTextStream *ts ) { QDate start = fromDate(); start.setYMD( start.year(), start.month(), 1 ); // go back to first day in month QDate end( start.year(), start.month(), start.daysInMonth() ); int startmonth = start.month(); int startyear = start.year(); while ( start < toDate() ) { // Write header QDate hDate( start.year(), start.month(), 1 ); QString hMon = hDate.toString( QLatin1String("MMMM") ); QString hYear = hDate.toString( QLatin1String("yyyy" )); *ts << "

    " << i18nc( "@title month and year", "%1 %2", hMon, hYear ) << "

    " << endl; if ( KGlobal::locale()->weekStartDay() == 1 ) { start = start.addDays( 1 - start.dayOfWeek() ); } else { if ( start.dayOfWeek() != 7 ) { start = start.addDays( -start.dayOfWeek() ); } } *ts << "" << endl; // Write table header *ts << " "; for ( int i=0; i < 7; ++i ) { *ts << ""; } *ts << "" << endl; // Write days while ( start <= end ) { *ts << " " << endl; for ( int i=0; i < 7; ++i ) { *ts << " " << endl; start = start.addDays( 1 ); } *ts << " " << endl; } *ts << "
    " << KGlobal::locale()->calendar()->weekDayName( start.addDays(i) ) << "
    "; *ts << "
    mHolidayMap.contains( start ) || start.dayOfWeek() == 7 ) { *ts << "class=\"dateholiday\""; } else { *ts << "class=\"date\""; } *ts << ">" << QString::number( start.day() ); if ( d->mHolidayMap.contains( start ) ) { *ts << " " << d->mHolidayMap[start] << ""; } *ts << "
    "; // Only print events within the from-to range if ( start >= fromDate() && start <= toDate() ) { KCalCore::Event::List events = d->mCalendar->events( start, d->mCalendar->timeSpec(), KCalCore::EventSortStartDate, KCalCore::SortDirectionAscending ); if ( events.count() ) { *ts << ""; foreach ( const KCalCore::Event::Ptr &event, events ) { Q_ASSERT( event ); if ( checkSecrecy( event ) ) { createEvent( ts, event, start, false ); } } *ts << "
    "; } else { *ts << " "; } } *ts << "
    " << endl; startmonth += 1; if ( startmonth > 12 ) { startyear += 1; startmonth = 1; } start.setYMD( startyear, startmonth, 1 ); end.setYMD( start.year(), start.month(), start.daysInMonth() ); } } void HtmlExportJob::createEventList( QTextStream *ts ) { int columns = 3; *ts << "" << endl; *ts << " " << endl; *ts << " " << endl; *ts << " " << endl; *ts << " " << endl; if ( d->mSettings->eventLocation() ) { *ts << " " << endl; ++columns; } if ( d->mSettings->eventCategories() ) { *ts << " " << endl; ++columns; } if ( d->mSettings->eventAttendees() ) { *ts << " " << endl; ++columns; } *ts << " " << endl; for ( QDate dt = fromDate(); dt <= toDate(); dt = dt.addDays(1) ) { kDebug() << "Getting events for" << dt.toString(); KCalCore::Event::List events = d->mCalendar->events( dt, d->mCalendar->timeSpec(), KCalCore::EventSortStartDate, KCalCore::SortDirectionAscending ); if ( !events.isEmpty() ) { *ts << " " << endl; foreach ( const KCalCore::Event::Ptr &event, events ) { Q_ASSERT( event ); if ( checkSecrecy( event ) ) { createEvent( ts, event, dt ); } } } } *ts << "
    " << i18nc( "@title:column event start time", "Start Time" ) << "" << i18nc( "@title:column event end time", "End Time" ) << "" << i18nc( "@title:column event description", "Event" ) << "" << i18nc( "@title:column event locatin", "Location" ) << "" << i18nc( "@title:column event categories", "Categories" ) << "" << i18nc( "@title:column event attendees", "Attendees" ) << "
    " << KGlobal::locale()->formatDate( dt ) << "
    " << endl; } void HtmlExportJob::createEvent ( QTextStream *ts, const KCalCore::Event::Ptr &event, QDate date, bool withDescription ) { kDebug() << event->summary(); *ts << " " << endl; if ( !event->allDay() ) { if ( event->isMultiDay( d->mCalendar->timeSpec() ) && ( event->dtStart().date() != date ) ) { *ts << "  " << endl; } else { *ts << " " << KCalUtils::IncidenceFormatter::timeToString( event->dtStart(), true, d->mCalendar->timeSpec() ) << "" << endl; } if ( event->isMultiDay( d->mCalendar->timeSpec() ) && ( event->dtEnd().date() != date ) ) { *ts << "  " << endl; } else { *ts << " " << KCalUtils::IncidenceFormatter::timeToString( event->dtEnd(), true, d->mCalendar->timeSpec() ) << "" << endl; } } else { *ts << "   " << endl; } *ts << " " << endl; *ts << " " << cleanChars( event->summary() ) << "" << endl; if ( withDescription && !event->description().isEmpty() ) { *ts << "

    " << breakString( cleanChars( event->description() ) ) << "

    " << endl; } *ts << " " << endl; if ( d->mSettings->eventLocation() ) { *ts << " " << endl; formatLocation( ts, event ); *ts << " " << endl; } if ( d->mSettings->eventCategories() ) { *ts << " " << endl; formatCategories( ts, event ); *ts << " " << endl; } if ( d->mSettings->eventAttendees() ) { *ts << " " << endl; formatAttendees( ts, event ); *ts << " " << endl; } *ts << " " << endl; } void HtmlExportJob::createTodoList ( QTextStream *ts ) { KCalCore::Todo::List rawTodoList = d->mCalendar->todos(); - int index = 0; - while ( index < rawTodoList.count() ) { - const KCalCore::Todo::Ptr todo = rawTodoList.value( index ); - Q_ASSERT( todo ); - const Akonadi::Item parentItem = d->mCalendar->item( todo->relatedTo() ); - - ++index; - } - // FIXME: Sort list by priorities. This is brute force and should be // replaced by a real sorting algorithm. KCalCore::Todo::List todoList; KCalCore::Todo::List::ConstIterator it; for ( int i = 1; i <= 9; ++i ) { foreach ( const KCalCore::Todo::Ptr &todo, rawTodoList ) { if ( todo->priority() == i && checkSecrecy( todo ) ) { todoList.append( todo ); } } } foreach ( const KCalCore::Todo::Ptr &todo, rawTodoList ) { if ( todo->priority() == 0 && checkSecrecy( todo ) ) { todoList.append( todo ); } } int columns = 3; *ts << "" << endl; *ts << " " << endl; *ts << " " << endl; *ts << " " << endl; *ts << " " << endl; if ( d->mSettings->taskDueDate() ) { *ts << " " << endl; ++columns; } if ( d->mSettings->taskLocation() ) { *ts << " " << endl; ++columns; } if ( d->mSettings->taskCategories() ) { *ts << " " << endl; ++columns; } if ( d->mSettings->taskAttendees() ) { *ts << " " << endl; ++columns; } *ts << " " << endl; // Create top-level list. for ( it = todoList.constBegin(); it != todoList.constEnd(); ++it ) { const QString parentUid = (*it)->relatedTo(); if ( parentUid.isEmpty() ) { createTodo( ts, *it ); } } //REVIEW(AKONADI_PORT) // relations/relatedTo usage: ok right now, as relations should yield // the same result as mCalendar->findChildren and items are not needed here // Create sub-level lists for ( it = todoList.constBegin(); it != todoList.constEnd(); ++it ) { - Akonadi::Item item = d->mCalendar->item( ( *it )->uid() ); + Akonadi::Item item = d->mCalendar->item( ( *it ) ); const Akonadi::Item::List relations = d->mCalendar->childItems( item.id() ); if ( !relations.isEmpty() ) { // Generate sub-to-do list *ts << " " << endl; *ts << " " << endl; *ts << " " << endl; KCalCore::Todo::List sortedList; // FIXME: Sort list by priorities. This is brute force and should be // replaced by a real sorting algorithm. for ( int i = 1; i <= 9; ++i ) { foreach ( const Akonadi::Item &item, relations ) { KCalCore::Todo::Ptr ev3 = CalendarSupport::todo( item ); if ( ev3 && ev3->priority() == i ) { sortedList.append( ev3 ); } } } foreach ( const Akonadi::Item &item, relations ) { KCalCore::Todo::Ptr ev3 = CalendarSupport::todo( item ); if ( ev3 && ev3->priority() == 0 ) { sortedList.append( ev3 ); } } KCalCore::Todo::List::ConstIterator it3; for ( it3 = sortedList.constBegin(); it3 != sortedList.constEnd(); ++it3 ) { createTodo( ts, *it3 ); } } } *ts << "
    " << i18nc( "@title:column", "To-do" ) << "" << i18nc( "@title:column to-do priority", "Priority" ) << "" << i18nc( "@title:column to-do percent completed", "Completed" ) << "" << i18nc( "@title:column to-do due date", "Due Date" ) << "" << i18nc( "@title:column to-do location", "Location" ) << "" << i18nc( "@title:column to-do categories", "Categories" ) << "" << i18nc( "@title:column to-do attendees", "Attendees" ) << "
    uid() << "\">" << i18nc( "@title:column sub-to-dos of the parent to-do", "Sub-To-dos of: " ) << "uid() << "\">" << cleanChars( (*it)->summary() ) << "
    " << endl; } void HtmlExportJob::createTodo( QTextStream *ts, const KCalCore::Todo::Ptr &todo ) { kDebug(); const bool completed = todo->isCompleted(); - Akonadi::Item it = d->mCalendar->item( todo->uid() ); + Akonadi::Item it = d->mCalendar->item( todo ); Akonadi::Item::List relations = d->mCalendar->childItems( it.id() ); *ts << "" << endl; *ts << " " << endl; *ts << " uid() << "\">" << endl; *ts << " " << cleanChars( todo->summary() ) << "" << endl; if ( !todo->description().isEmpty() ) { *ts << "

    " << breakString( cleanChars( todo->description() ) ) << "

    " << endl; } if ( relations.count() ) { *ts << " " << endl; } *ts << " " << endl; *ts << " " << endl; *ts << " " << todo->priority() << endl; *ts << " " << endl; *ts << " " << endl; *ts << " " << i18nc( "@info/plain to-do percent complete", "%1 %", todo->percentComplete() ) << endl; *ts << " " << endl; if ( d->mSettings->taskDueDate() ) { *ts << " " << endl; if ( todo->hasDueDate() ) { *ts << " " << KCalUtils::IncidenceFormatter::dateToString( todo->dtDue( true ) ) << endl; } else { *ts << "  " << endl; } *ts << " " << endl; } if ( d->mSettings->taskLocation() ) { *ts << " " << endl; formatLocation( ts, todo ); *ts << " " << endl; } if ( d->mSettings->taskCategories() ) { *ts << " " << endl; formatCategories( ts, todo ); *ts << " " << endl; } if ( d->mSettings->taskAttendees() ) { *ts << " " << endl; formatAttendees( ts, todo ); *ts << " " << endl; } *ts << "" << endl; } void HtmlExportJob::createWeekView( QTextStream *ts ) { Q_UNUSED( ts ); // FIXME: Implement this! } void HtmlExportJob::createJournalView( QTextStream *ts ) { Q_UNUSED( ts ); // Journal::List rawJournalList = d->mCalendar->journals(); // FIXME: Implement this! } void HtmlExportJob::createFreeBusyView( QTextStream *ts ) { Q_UNUSED( ts ); // FIXME: Implement this! } bool HtmlExportJob::checkSecrecy( const KCalCore::Incidence::Ptr &incidence ) { int secrecy = incidence->secrecy(); if ( secrecy == KCalCore::Incidence::SecrecyPublic ) { return true; } if ( secrecy == KCalCore::Incidence::SecrecyPrivate && !d->mSettings->excludePrivate() ) { return true; } if ( secrecy == KCalCore::Incidence::SecrecyConfidential && !d->mSettings->excludeConfidential() ) { return true; } return false; } void HtmlExportJob::formatLocation( QTextStream *ts, const KCalCore::Incidence::Ptr &incidence ) { if ( !incidence->location().isEmpty() ) { *ts << " " << cleanChars( incidence->location() ) << endl; } else { *ts << "  " << endl; } } void HtmlExportJob::formatCategories( QTextStream *ts, const KCalCore::Incidence::Ptr &incidence ) { if ( !incidence->categoriesStr().isEmpty() ) { *ts << " " << cleanChars( incidence->categoriesStr() ) << endl; } else { *ts << "  " << endl; } } void HtmlExportJob::formatAttendees( QTextStream *ts, const KCalCore::Incidence::Ptr &incidence ) { KCalCore::Attendee::List attendees = incidence->attendees(); if ( attendees.count() ) { *ts << ""; const KABC::Addressee organizer = d->mOrganizersMap.value( incidence->uid() ); if ( !organizer.isEmpty() ) { *ts << "organizer()->email() << "\">"; *ts << cleanChars( organizer.formattedName() ) << "" << endl; } else { *ts << incidence->organizer()->fullName(); } *ts << "
    "; KCalCore::Attendee::List::ConstIterator it; for ( it = attendees.constBegin(); it != attendees.constEnd(); ++it ) { KCalCore::Attendee::Ptr a = *it; if ( !a->email().isEmpty() ) { *ts << "email(); *ts << "\">" << cleanChars( a->name() ) << ""; } else { *ts << " " << cleanChars( a->name() ); } *ts << "
    " << endl; } } else { *ts << "  " << endl; } } QString HtmlExportJob::breakString( const QString &text ) { int number = text.count( QLatin1Char('\n') ); if ( number <= 0 ) { return text; } else { QString out; QString tmpText = text; QString tmp; for ( int i = 0; i <= number; ++i ) { const int pos = tmpText.indexOf( QLatin1Char('\n') ); tmp = tmpText.left( pos ); tmpText = tmpText.right( tmpText.length() - pos - 1 ); out += tmp + QLatin1String("
    "); } return out; } } void HtmlExportJob::createFooter( QTextStream *ts ) { // FIXME: Implement this in a translatable way! QString trailer = i18nc( "@info/plain", "This page was created " ); /* bool hasPerson = false; bool hasCredit = false; bool hasCreditURL = false; QString mail, name, credit, creditURL;*/ if ( !d->mSettings->eMail().isEmpty() ) { if ( !d->mSettings->name().isEmpty() ) { trailer += i18nc( "@info/plain page creator email link with name", "by %2 ", d->mSettings->eMail(), d->mSettings->name() ); } else { trailer += i18nc( "@info/plain page creator email link", "by %2 ", d->mSettings->eMail(), d->mSettings->eMail() ); } } else { if ( !d->mSettings->name().isEmpty() ) { trailer += i18nc( "@info/plain page creator name only", "by %1 ", d->mSettings->name() ); } } if ( !d->mSettings->creditName().isEmpty() ) { if ( !d->mSettings->creditURL().isEmpty() ) { trailer += i18nc( "@info/plain page credit with name and link", "with %2", d->mSettings->creditURL(), d->mSettings->creditName() ); } else { trailer += i18nc( "@info/plain page credit name only", "with %1", d->mSettings->creditName() ); } } *ts << "

    " << trailer << "

    " << endl; } QString cleanChars( const QString &text ) { QString txt = text; txt = txt.replace( QLatin1Char('&'), QLatin1String("&") ); txt = txt.replace( QLatin1Char('<'), QLatin1String("<") ); txt = txt.replace( QLatin1Char('>'), QLatin1String(">") ); txt = txt.replace( QLatin1Char('\"'), QLatin1String(""") ); txt = txt.replace( QString::fromUtf8( "ä" ), QLatin1String("ä") ); txt = txt.replace( QString::fromUtf8( "Ä" ), QLatin1String("Ä") ); txt = txt.replace( QString::fromUtf8( "ö" ), QLatin1String("ö") ); txt = txt.replace( QString::fromUtf8( "Ö" ), QLatin1String("Ö") ); txt = txt.replace( QString::fromUtf8( "ü" ), QLatin1String("ü") ); txt = txt.replace( QString::fromUtf8( "Ü" ), QLatin1String("Ü") ); txt = txt.replace( QString::fromUtf8( "ß" ), QLatin1String("ß") ); txt = txt.replace( QString::fromUtf8( "€" ), QLatin1String("€") ); txt = txt.replace( QString::fromUtf8( "é" ), QLatin1String("é") ); return txt; } QString HtmlExportJob::styleSheet() const { if ( !d->mSettings->styleSheet().isEmpty() ) { return d->mSettings->styleSheet(); } QString css; if ( QApplication::isRightToLeft() ) { css += QLatin1String(" body { background-color:white; color:black; direction: rtl }\n"); css += QLatin1String(" td { text-align:center; background-color:#eee }\n"); css += QLatin1String(" th { text-align:center; background-color:#228; color:white }\n"); css += QLatin1String(" td.sumdone { background-color:#ccc }\n"); css += QLatin1String(" td.done { background-color:#ccc }\n"); css += QLatin1String(" td.subhead { text-align:center; background-color:#ccf }\n"); css += QLatin1String(" td.datehead { text-align:center; background-color:#ccf }\n"); css += QLatin1String(" td.space { background-color:white }\n"); css += QLatin1String(" td.dateholiday { color:red }\n"); } else { css += QLatin1String(" body { background-color:white; color:black }\n"); css += QLatin1String( " td { text-align:center; background-color:#eee }\n"); css += QLatin1String( " th { text-align:center; background-color:#228; color:white }\n"); css += QLatin1String( " td.sum { text-align:left }\n"); css += QLatin1String( " td.sumdone { text-align:left; background-color:#ccc }\n"); css += QLatin1String( " td.done { background-color:#ccc }\n"); css += QLatin1String( " td.subhead { text-align:center; background-color:#ccf }\n"); css += QLatin1String( " td.datehead { text-align:center; background-color:#ccf }\n"); css += QLatin1String( " td.space { background-color:white }\n"); css += QLatin1String( " td.date { text-align:left }\n"); css += QLatin1String( " td.dateholiday { text-align:left; color:red }\n"); } return css; } void HtmlExportJob::addHoliday( const QDate &date, const QString &name ) { if ( d->mHolidayMap[date].isEmpty() ) { d->mHolidayMap[date] = name; } else { d->mHolidayMap[date] = i18nc( "@info/plain holiday by date and name", "%1, %2", d->mHolidayMap[date], name ); } } QDate HtmlExportJob::fromDate() const { return d->mSettings->dateStart().date(); } QDate HtmlExportJob::toDate() const { return d->mSettings->dateEnd().date(); } HTMLExportSettings *HtmlExportJob::settings() const { return d->mSettings; } diff --git a/korganizer/searchdialog.cpp b/korganizer/searchdialog.cpp index be5aafbe06..d9b1f14566 100644 --- a/korganizer/searchdialog.cpp +++ b/korganizer/searchdialog.cpp @@ -1,258 +1,258 @@ /* This file is part of KOrganizer. Copyright (c) 1998 Preston Brown Copyright (c) 2000,2001 Cornelius Schumacher Copyright (C) 2003-2004 Reinhold Kainhofer This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #include "searchdialog.h" #include "ui_searchdialog_base.h" #include "calendarview.h" #include "koglobals.h" #include #include #include #include using namespace KOrg; SearchDialog::SearchDialog( CalendarView *calendarview ) : KDialog( calendarview ), m_ui( new Ui::SearchDialog ), m_calendarview( calendarview ) { setCaption( i18n( "Search Calendar" ) ); setModal( false ); QWidget *mainWidget = new QWidget( this ); m_ui->setupUi( mainWidget ); setMainWidget( mainWidget ); // Set nice initial start and end dates for the search const QDate currDate = QDate::currentDate(); m_ui->startDate->setDate( currDate ); m_ui->endDate->setDate( currDate.addYears( 1 ) ); connect( m_ui->searchEdit, SIGNAL(textChanged(QString)), this, SLOT(searchTextChanged(QString)) ); // Results list view QVBoxLayout *layout = new QVBoxLayout; layout->setMargin( 0 ); listView = new EventViews::ListView( m_calendarview->calendar(), this ); layout->addWidget( listView ); m_ui->listViewFrame->setLayout( layout ); connect( this, SIGNAL(user1Clicked()), SLOT(doSearch()) ); // Propagate edit and delete event signals from event list view connect( listView, SIGNAL(showIncidenceSignal(Akonadi::Item)), SIGNAL(showIncidenceSignal(Akonadi::Item)) ); connect( listView, SIGNAL(editIncidenceSignal(Akonadi::Item, KDateTime)), SIGNAL(editIncidenceSignal(Akonadi::Ite, KDateTimem)) ); connect( listView, SIGNAL(deleteIncidenceSignal(Akonadi::Item)), SIGNAL(deleteIncidenceSignal(Akonadi::Item)) ); readConfig(); setButtons( User1 | Cancel ); setDefaultButton( User1 ); setButtonGuiItem( User1, KGuiItem( i18nc( "search in calendar", "&Search" ), QLatin1String( "edit-find" ) ) ); setButtonToolTip( User1, i18n( "Start searching" ) ); showButtonSeparator( false ); } SearchDialog::~SearchDialog() { writeConfig(); } void SearchDialog::showEvent( QShowEvent *event ) { Q_UNUSED( event ); m_ui->searchEdit->setFocus(); } void SearchDialog::searchTextChanged( const QString &_text ) { enableButton( KDialog::User1, !_text.isEmpty() ); } void SearchDialog::doSearch() { QRegExp re; re.setPatternSyntax( QRegExp::Wildcard ); // most people understand these better. re.setCaseSensitivity( Qt::CaseInsensitive ); re.setPattern( m_ui->searchEdit->text() ); if ( !re.isValid() ) { KMessageBox::sorry( this, i18n( "Invalid search expression, cannot perform the search. " "Please enter a search expression using the wildcard characters " "'*' and '?' where needed." ) ); return; } search( re ); listView->showIncidences( mMatchedEvents, QDate() ); if ( mMatchedEvents.isEmpty() ) { m_ui->numItems->setText ( QString() ); KMessageBox::information( this, i18n( "No items were found that match your search pattern." ), i18n( "Search Results" ), QLatin1String( "NoSearchResults" ) ); } else { m_ui->numItems->setText( i18np( "%1 item","%1 items", mMatchedEvents.count() ) ); } } void SearchDialog::updateView() { QRegExp re; re.setPatternSyntax( QRegExp::Wildcard ); // most people understand these better. re.setCaseSensitivity( Qt::CaseInsensitive ); re.setPattern( m_ui->searchEdit->text() ); if ( re.isValid() ) { search( re ); } else { mMatchedEvents.clear(); } listView->showIncidences( mMatchedEvents, QDate() ); } void SearchDialog::search( const QRegExp &re ) { const QDate startDt = m_ui->startDate->date(); const QDate endDt = m_ui->endDate->date(); KCalCore::Event::List events; KDateTime::Spec timeSpec = CalendarSupport::KCalPrefs::instance()->timeSpec(); if ( m_ui->eventsCheck->isChecked() ) { events = m_calendarview->calendar()->events( startDt, endDt, timeSpec, m_ui->inclusiveCheck->isChecked() ); } KCalCore::Todo::List todos; if ( m_ui->todosCheck->isChecked() ) { if ( m_ui->includeUndatedTodos->isChecked() ) { KDateTime::Spec spec = CalendarSupport::KCalPrefs::instance()->timeSpec(); KCalCore::Todo::List alltodos = m_calendarview->calendar()->todos(); Q_FOREACH ( const KCalCore::Todo::Ptr &todo, alltodos ) { Q_ASSERT( todo ); if ( ( !todo->hasStartDate() && !todo->hasDueDate() ) || // undated ( todo->hasStartDate() && ( todo->dtStart().toTimeSpec( spec ).date() >= startDt ) && ( todo->dtStart().toTimeSpec( spec ).date() <= endDt ) ) || //start dt in range ( todo->hasDueDate() && ( todo->dtDue().toTimeSpec( spec ).date() >= startDt ) && ( todo->dtDue().toTimeSpec( spec ).date() <= endDt ) ) || //due dt in range ( todo->hasCompletedDate() && ( todo->completed().toTimeSpec( spec ).date() >= startDt ) && ( todo->completed().toTimeSpec( spec ).date() <= endDt ) ) ) {//completed dt in range todos.append( todo ); } } } else { QDate dt = startDt; while ( dt <= endDt ) { todos += m_calendarview->calendar()->todos( dt ); dt = dt.addDays( 1 ); } } } KCalCore::Journal::List journals; if ( m_ui->journalsCheck->isChecked() ) { QDate dt = startDt; while ( dt <= endDt ) { journals += m_calendarview->calendar()->journals( dt ); dt = dt.addDays( 1 ); } } mMatchedEvents.clear(); KCalCore::Incidence::List incidences = Akonadi::ETMCalendar::mergeIncidenceList( events, todos, journals ); Q_FOREACH ( const KCalCore::Incidence::Ptr &ev, incidences ) { Q_ASSERT( ev ); - Akonadi::Item item = m_calendarview->calendar()->item( ev->uid() ); + Akonadi::Item item = m_calendarview->calendar()->item( ev ); if ( m_ui->summaryCheck->isChecked() ) { if ( re.indexIn( ev->summary() ) != -1 ) { mMatchedEvents.append( item ); continue; } } if ( m_ui->descriptionCheck->isChecked() ) { if ( re.indexIn( ev->description() ) != -1 ) { mMatchedEvents.append( item ); continue; } } if ( m_ui->categoryCheck->isChecked() ) { if ( re.indexIn( ev->categoriesStr() ) != -1 ) { mMatchedEvents.append( item ); continue; } } if ( m_ui->locationCheck->isChecked() ) { if ( re.indexIn( ev->location() ) != -1 ) { mMatchedEvents.append( item ); continue; } } if ( m_ui->attendeeCheck->isChecked() ) { Q_FOREACH ( const KCalCore::Attendee::Ptr &attendee, ev->attendees() ) { if ( re.indexIn( attendee->fullName() ) != -1 ) { mMatchedEvents.append( item ); break; } } } } } void SearchDialog::readConfig() { KConfigGroup group( KOGlobals::self()->config(), QLatin1String( "SearchDialog" ) ); const QSize size = group.readEntry( "Size", QSize( 775, 600 ) ); if ( size.isValid() ) { resize( size ); } } void SearchDialog::writeConfig() { KConfigGroup group( KOGlobals::self()->config(), QLatin1String( "SearchDialog" ) ); group.writeEntry( "Size", size() ); group.sync(); } diff --git a/korganizer/views/collectionview/quickview.cpp b/korganizer/views/collectionview/quickview.cpp index de3e43e43b..a34ed7dfa5 100644 --- a/korganizer/views/collectionview/quickview.cpp +++ b/korganizer/views/collectionview/quickview.cpp @@ -1,189 +1,193 @@ /* * Copyright 2014 Sandro Knauß * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ //http://stackoverflow.com/questions/18831242/qt-start-editing-of-cell-after-one-click #include "quickview.h" #include "ui_quickview.h" #include #include #include #include #include #include #include -#include #include #include #include #include #include class FreebusyViewCalendar : public EventViews::ViewCalendar { public: virtual ~FreebusyViewCalendar() {}; virtual bool isValid(const KCalCore::Incidence::Ptr &incidence) const { return isValid(incidence->uid()); } virtual bool isValid(const QString &incidenceIdentifier) const { return incidenceIdentifier.startsWith(QLatin1String("fb-")); } virtual QString displayName(const KCalCore::Incidence::Ptr &incidence) const { Q_UNUSED(incidence); return i18n("Freebusycalendar from %1").arg(name); } virtual QColor resourceColor(const KCalCore::Incidence::Ptr &incidence) const { bool ok = false; int status = incidence->customProperty("FREEBUSY", "STATUS").toInt(&ok); if (!ok) { return QColor("#555"); } switch (status) { case KCalCore::FreeBusyPeriod::Busy: return QColor("#f00"); case KCalCore::FreeBusyPeriod::BusyTentative: case KCalCore::FreeBusyPeriod::BusyUnavailable: return QColor("#f70"); case KCalCore::FreeBusyPeriod::Free: return QColor("#0f0"); default: return QColor("#555"); } } + virtual QString uid(const KCalCore::Incidence::Ptr &incidence) const + { + return incidence->uid(); + } + virtual QString iconForIncidence(const KCalCore::Incidence::Ptr &incidence) const { return QString(); } virtual KCalCore::Calendar::Ptr getCalendar() const { return mCalendar; } KCalCore::Calendar::Ptr mCalendar; QString name; }; Quickview::Quickview(const Person &person, const Akonadi::Collection &col) : KDialog() , mUi(new Ui_quickview) , mPerson(person) , mCollection(col) , mDayRange(7) { setButtons(KDialog::Close); QWidget *w = new QWidget( this ); mUi->setupUi( w ); setMainWidget( w ); mAgendaView = new EventViews::AgendaView(QDate(), QDate(), false, false); //show fbcalendar for person in quickview if (!person.mail.isEmpty()) { FreeBusyItemModel *model = new FreeBusyItemModel(this); FreeBusyCalendar *fbCal = new FreeBusyCalendar(this); FreebusyViewCalendar *fbCalendar = new FreebusyViewCalendar(); KCalCore::Attendee::Ptr attendee(new KCalCore::Attendee(person.name, person.mail)); FreeBusyItem::Ptr freebusy( new FreeBusyItem( attendee, this )); fbCal->setModel(model); model->addItem(freebusy); fbCalendar->mCalendar = fbCal->calendar(); fbCalendar->name = attendee->fullName(); mAgendaView->addCalendar(EventViews::ViewCalendar::Ptr(fbCalendar)); } if (mCollection.isValid()) { //create etm for mCollection Akonadi::ChangeRecorder *monitor = new Akonadi::ChangeRecorder(this); Akonadi::ItemFetchScope scope; QStringList allMimeTypes; allMimeTypes << KCalCore::Event::eventMimeType() << KCalCore::Todo::todoMimeType() << KCalCore::Journal::journalMimeType(); scope.fetchFullPayload(true); scope.fetchAttribute(); monitor->setCollectionMonitored(mCollection); monitor->fetchCollection(true); monitor->setItemFetchScope(scope); monitor->setAllMonitored(true); foreach(const QString &mimetype, allMimeTypes) { monitor->setMimeTypeMonitored(mimetype, true); } Akonadi::ETMCalendar::Ptr calendar = Akonadi::ETMCalendar::Ptr(new Akonadi::ETMCalendar(monitor)); calendar->setCollectionFilteringEnabled(false); mAgendaView->setCalendar(calendar); } mUi->calender->addWidget( mAgendaView ); connect(mUi->mTodayBtn, SIGNAL(clicked(bool)), SLOT(onTodayClicked())); connect(mUi->mNextBtn, SIGNAL(clicked(bool)), SLOT(onNextClicked())); connect(mUi->mPreviousBtn, SIGNAL(clicked(bool)), SLOT(onPreviousClicked())); onTodayClicked(); } Quickview::~Quickview() { delete mUi; } void Quickview::onNextClicked() { QDate start = mAgendaView->startDate().addDays(mDayRange); mAgendaView->showDates(start, start.addDays(mDayRange-1)); } void Quickview::onPreviousClicked() { QDate start = mAgendaView->startDate().addDays(-mDayRange); mAgendaView->showDates(start, start.addDays(mDayRange-1)); } void Quickview::onTodayClicked() { QDate start = QDate::currentDate(); start = start.addDays(-QDate::currentDate().dayOfWeek()+1); mAgendaView->showDates(start, start.addDays(mDayRange-1)); } #include "quickview.moc" diff --git a/korganizer/views/collectionview/quickview.h b/korganizer/views/collectionview/quickview.h index d296887e0f..a2464f1bb5 100644 --- a/korganizer/views/collectionview/quickview.h +++ b/korganizer/views/collectionview/quickview.h @@ -1,62 +1,62 @@ /* * Copyright 2014 Sandro Knauß * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #ifndef KORG_QUICKVIEW_H #define KORG_QUICKVIEW_H #include "controller.h" -#include +#include #include #include #include #include class Ui_quickview; namespace EventViews { class AgendaView; } class Quickview : public KDialog { Q_OBJECT public: Quickview(const Person &person, const Akonadi::Collection &col); virtual ~Quickview(); private slots: void onTodayClicked(); void onNextClicked(); void onPreviousClicked(); private: Ui_quickview *mUi; EventViews::AgendaView *mAgendaView; Person mPerson; Akonadi::Collection mCollection; int mDayRange; }; #endif // QUICKVIEW_H diff --git a/korganizer/views/monthview/monthview.cpp b/korganizer/views/monthview/monthview.cpp index 277b756fcb..3184377a41 100644 --- a/korganizer/views/monthview/monthview.cpp +++ b/korganizer/views/monthview/monthview.cpp @@ -1,247 +1,247 @@ /* This file is part of KOrganizer. Copyright (C) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net Author: Sergio Martins This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #include "monthview.h" #include "koeventpopupmenu.h" #include "koprefs.h" #include #include using namespace KOrg; class MonthView::Private { public: Private( MonthView *qq ) : q( qq ) { QVBoxLayout *layout = new QVBoxLayout( q ); layout->setMargin( 0 ); mMonthView = new EventViews::MonthView( EventViews::MonthView::Visible, q ); mMonthView->setPreferences( KOPrefs::instance()->eventViewsPreferences() ); layout->addWidget( mMonthView ); mPopup = q->eventPopup(); } EventViews::MonthView *mMonthView; KOEventPopupMenu *mPopup; private: MonthView *q; }; MonthView::MonthView( QWidget *parent ) : KOEventView( parent ), d( new Private( this ) ) { connect( d->mMonthView, SIGNAL(showIncidencePopupSignal(Akonadi::Item,QDate)), d->mPopup, SLOT(showIncidencePopup(Akonadi::Item,QDate)) ); connect( d->mMonthView, SIGNAL(showNewEventPopupSignal()), SLOT(showNewEventPopup()) ); connect( d->mMonthView, SIGNAL(datesSelected(KCalCore::DateList)), SIGNAL(datesSelected(KCalCore::DateList)) ); connect( d->mMonthView, SIGNAL(shiftedEvent(QDate,QDate)), SIGNAL(shiftedEvent(QDate,QDate)) ); connect( d->mMonthView, SIGNAL(incidenceSelected(Akonadi::Item,QDate)), SIGNAL(incidenceSelected(Akonadi::Item,QDate)) ); connect( d->mMonthView, SIGNAL(showIncidenceSignal(Akonadi::Item)), SIGNAL(showIncidenceSignal(Akonadi::Item)) ); - connect( d->mMonthView, SIGNAL(editIncidenceSignal(Akonadi::Item)), - SIGNAL(editIncidenceSignal(Akonadi::Item)) ); + connect( d->mMonthView, SIGNAL(editIncidenceSignal(Akonadi::Item, KDateTime)), + SIGNAL(editIncidenceSignal(Akonadi::Item, KDateTime)) ); connect( d->mMonthView, SIGNAL(deleteIncidenceSignal(Akonadi::Item)), SIGNAL(deleteIncidenceSignal(Akonadi::Item)) ); connect( d->mMonthView, SIGNAL(cutIncidenceSignal(Akonadi::Item)), SIGNAL(cutIncidenceSignal(Akonadi::Item)) ); connect( d->mMonthView, SIGNAL(copyIncidenceSignal(Akonadi::Item)), SIGNAL(copyIncidenceSignal(Akonadi::Item)) ); connect( d->mMonthView, SIGNAL(pasteIncidenceSignal()), SIGNAL(pasteIncidenceSignal()) ); connect( d->mMonthView, SIGNAL(toggleAlarmSignal(Akonadi::Item)), SIGNAL(toggleAlarmSignal(Akonadi::Item)) ); connect( d->mMonthView, SIGNAL(toggleTodoCompletedSignal(Akonadi::Item)), SIGNAL(toggleTodoCompletedSignal(Akonadi::Item)) ); connect( d->mMonthView, SIGNAL(copyIncidenceToResourceSignal(Akonadi::Item,QString)), SIGNAL(copyIncidenceToResourceSignal(Akonadi::Item,QString)) ); connect( d->mMonthView, SIGNAL(moveIncidenceToResourceSignal(Akonadi::Item,QString)), SIGNAL(moveIncidenceToResourceSignal(Akonadi::Item,QString)) ); connect( d->mMonthView, SIGNAL(dissociateOccurrencesSignal(Akonadi::Item,QDate)), SIGNAL(dissociateOccurrencesSignal(Akonadi::Item,QDate)) ); connect( d->mMonthView, SIGNAL(newEventSignal()), SIGNAL(newEventSignal()) ); connect( d->mMonthView, SIGNAL(newEventSignal(QDate)), SIGNAL(newEventSignal(QDate)) ); connect( d->mMonthView, SIGNAL(newEventSignal(QDateTime)), SIGNAL(newEventSignal(QDateTime)) ); connect( d->mMonthView, SIGNAL(newEventSignal(QDateTime,QDateTime)), SIGNAL(newEventSignal(QDateTime,QDateTime)) ); connect( d->mMonthView, SIGNAL(newTodoSignal(QDate)), SIGNAL(newTodoSignal(QDate)) ); connect( d->mMonthView, SIGNAL(newSubTodoSignal(Akonadi::Item)), SIGNAL(newSubTodoSignal(Akonadi::Item)) ); connect( d->mMonthView, SIGNAL(newJournalSignal(QDate)), SIGNAL(newJournalSignal(QDate)) ); connect( d->mMonthView, SIGNAL(fullViewChanged(bool)), SIGNAL(fullViewChanged(bool)) ); } MonthView::~MonthView() { delete d; } CalendarSupport::CalPrinterBase::PrintType MonthView::printType() const { return CalendarSupport::CalPrinterBase::Month; } int MonthView::currentDateCount() const { return d->mMonthView->currentDateCount(); } int MonthView::currentMonth() const { return d->mMonthView->currentMonth(); } KCalCore::DateList MonthView::selectedIncidenceDates() { return d->mMonthView->selectedIncidenceDates(); } QDateTime MonthView::selectionStart() { return d->mMonthView->selectionStart(); } QDateTime MonthView::selectionEnd() { return d->mMonthView->selectionEnd(); } bool MonthView::eventDurationHint( QDateTime &startDt, QDateTime &endDt, bool &allDay ) { return d->mMonthView->eventDurationHint( startDt, endDt, allDay ); } QDate MonthView::averageDate() const { return d->mMonthView->averageDate(); } bool MonthView::usesFullWindow() { return d->mMonthView->usesFullWindow(); } bool MonthView::supportsDateRangeSelection() { return d->mMonthView->supportsDateRangeSelection(); } void MonthView::updateView() { d->mMonthView->updateView(); } void MonthView::showIncidences( const Akonadi::Item::List &incidenceList, const QDate &date ) { d->mMonthView->showIncidences( incidenceList, date ); } void MonthView::changeIncidenceDisplay( const Akonadi::Item &item, Akonadi::IncidenceChanger::ChangeType changeType ) { d->mMonthView->changeIncidenceDisplay( item, changeType ); } void MonthView::updateConfig() { d->mMonthView->updateConfig(); } int MonthView::maxDatesHint() const { return 6 * 7; } Akonadi::Item::List MonthView::selectedIncidences() { return d->mMonthView->selectedIncidences(); } void MonthView::setTypeAheadReceiver( QObject *o ) { d->mMonthView->setTypeAheadReceiver( o ); } void MonthView::setDateRange( const KDateTime &start, const KDateTime &end, const QDate &preferredMonth ) { d->mMonthView->setDateRange( start, end, preferredMonth ); } void MonthView::setCalendar( const Akonadi::ETMCalendar::Ptr &cal ) { KOEventView::setCalendar( cal ); d->mPopup->setCalendar( cal ); d->mMonthView->setCalendar( cal ); } void MonthView::setIncidenceChanger( Akonadi::IncidenceChanger *changer ) { d->mMonthView->setIncidenceChanger( changer ); } void MonthView::showDates( const QDate &start, const QDate &end, const QDate &preferredMonth ) { Q_UNUSED( start ); Q_UNUSED( end ); Q_UNUSED( preferredMonth ); } diff --git a/korganizer/views/multiagendaview/multiagendaview.cpp b/korganizer/views/multiagendaview/multiagendaview.cpp index 5112af7c70..ec91218c94 100644 --- a/korganizer/views/multiagendaview/multiagendaview.cpp +++ b/korganizer/views/multiagendaview/multiagendaview.cpp @@ -1,537 +1,537 @@ /* This file is part of KOrganizer. Copyright (c) 2007 Volker Krause This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "multiagendaview.h" #include "akonadicollectionview.h" #include "koeventpopupmenu.h" #include "koprefs.h" #include "ui_multiagendaviewconfigwidget.h" #include #include #include #include #include #include #include #include #include using namespace Future; using namespace KOrg; static QString generateColumnLabel( int c ) { return i18n( "Agenda %1", c + 1 ); } class MultiAgendaView::Private { public: Private( MultiAgendaView *qq ) : q( qq ) { QHBoxLayout *layout = new QHBoxLayout( q ); mMultiAgendaView = new EventViews::MultiAgendaView( q ); mMultiAgendaView->setPreferences( KOPrefs::instance()->eventViewsPreferences() ); layout->addWidget( mMultiAgendaView ); mPopup = q->eventPopup(); } EventViews::MultiAgendaView *mMultiAgendaView; KOEventPopupMenu *mPopup; private: MultiAgendaView * const q; }; MultiAgendaView::MultiAgendaView( QWidget *parent ) : KOEventView( parent ), d( new Private( this ) ) { connect( d->mMultiAgendaView, SIGNAL(datesSelected(KCalCore::DateList)), SIGNAL(datesSelected(KCalCore::DateList)) ); connect( d->mMultiAgendaView, SIGNAL(shiftedEvent(QDate,QDate)), SIGNAL(shiftedEvent(QDate,QDate)) ); connect( d->mMultiAgendaView, SIGNAL(showIncidencePopupSignal(Akonadi::Item,QDate)), d->mPopup, SLOT(showIncidencePopup(Akonadi::Item,QDate)) ); connect( d->mMultiAgendaView, SIGNAL(showNewEventPopupSignal()), SLOT(showNewEventPopup()) ); connect( d->mMultiAgendaView, SIGNAL(incidenceSelected(Akonadi::Item,QDate)), SIGNAL(incidenceSelected(Akonadi::Item,QDate)) ); connect( d->mMultiAgendaView, SIGNAL(showIncidenceSignal(Akonadi::Item)), SIGNAL(showIncidenceSignal(Akonadi::Item)) ); - connect( d->mMultiAgendaView, SIGNAL(editIncidenceSignal(Akonadi::Item)), - SIGNAL(editIncidenceSignal(Akonadi::Item)) ); + connect( d->mMultiAgendaView, SIGNAL(editIncidenceSignal(Akonadi::Item, KDateTime)), + SIGNAL(editIncidenceSignal(Akonadi::Item, KDateTime)) ); connect( d->mMultiAgendaView, SIGNAL(deleteIncidenceSignal(Akonadi::Item)), SIGNAL(deleteIncidenceSignal(Akonadi::Item)) ); connect( d->mMultiAgendaView, SIGNAL(cutIncidenceSignal(Akonadi::Item)), SIGNAL(cutIncidenceSignal(Akonadi::Item)) ); connect( d->mMultiAgendaView, SIGNAL(copyIncidenceSignal(Akonadi::Item)), SIGNAL(copyIncidenceSignal(Akonadi::Item)) ); connect( d->mMultiAgendaView, SIGNAL(pasteIncidenceSignal()), SIGNAL(pasteIncidenceSignal()) ); connect( d->mMultiAgendaView, SIGNAL(toggleAlarmSignal(Akonadi::Item)), SIGNAL(toggleAlarmSignal(Akonadi::Item)) ); connect( d->mMultiAgendaView, SIGNAL(toggleTodoCompletedSignal(Akonadi::Item)), SIGNAL(toggleTodoCompletedSignal(Akonadi::Item)) ); connect( d->mMultiAgendaView, SIGNAL(copyIncidenceToResourceSignal(Akonadi::Item,QString)), SIGNAL(copyIncidenceToResourceSignal(Akonadi::Item,QString)) ); connect( d->mMultiAgendaView, SIGNAL(moveIncidenceToResourceSignal(Akonadi::Item,QString)), SIGNAL(moveIncidenceToResourceSignal(Akonadi::Item,QString)) ); connect( d->mMultiAgendaView, SIGNAL(dissociateOccurrencesSignal(Akonadi::Item,QDate)), SIGNAL(dissociateOccurrencesSignal(Akonadi::Item,QDate)) ); connect( d->mMultiAgendaView, SIGNAL(newEventSignal()), SIGNAL(newEventSignal()) ); connect( d->mMultiAgendaView, SIGNAL(newEventSignal(QDate)), SIGNAL(newEventSignal(QDate)) ); connect( d->mMultiAgendaView, SIGNAL(newEventSignal(QDateTime)), SIGNAL(newEventSignal(QDateTime)) ); connect( d->mMultiAgendaView, SIGNAL(newEventSignal(QDateTime,QDateTime)), SIGNAL(newEventSignal(QDateTime,QDateTime)) ); connect( d->mMultiAgendaView, SIGNAL(newTodoSignal(QDate)), SIGNAL(newTodoSignal(QDate)) ); connect( d->mMultiAgendaView, SIGNAL(newSubTodoSignal(Akonadi::Item)), SIGNAL(newSubTodoSignal(Akonadi::Item)) ); connect( d->mMultiAgendaView, SIGNAL(newJournalSignal(QDate)), SIGNAL(newJournalSignal(QDate)) ); } void MultiAgendaView::setCalendar( const Akonadi::ETMCalendar::Ptr &cal ) { d->mMultiAgendaView->setCalendar( cal ); d->mPopup->setCalendar( cal ); } MultiAgendaView::~MultiAgendaView() { delete d; } Akonadi::Item::List MultiAgendaView::selectedIncidences() { return d->mMultiAgendaView->selectedIncidences(); } KCalCore::DateList MultiAgendaView::selectedIncidenceDates() { return d->mMultiAgendaView->selectedIncidenceDates(); } int MultiAgendaView::currentDateCount() const { return d->mMultiAgendaView->currentDateCount(); } void MultiAgendaView::showDates( const QDate &start, const QDate &end, const QDate & ) { d->mMultiAgendaView->showDates( start, end ); } void MultiAgendaView::showIncidences( const Akonadi::Item::List &incidenceList, const QDate &date ) { d->mMultiAgendaView->showIncidences( incidenceList, date ); } void MultiAgendaView::updateView() { d->mMultiAgendaView->updateView(); } Akonadi::Collection::Id MultiAgendaView::collectionId() const { return d->mMultiAgendaView->collectionId(); } void MultiAgendaView::changeIncidenceDisplay( const Akonadi::Item &, Akonadi::IncidenceChanger::ChangeType ) { } int MultiAgendaView::maxDatesHint() const { return EventViews::AgendaView::MAX_DAY_COUNT; } void MultiAgendaView::setDateRange( const KDateTime &start, const KDateTime &end, const QDate & ) { d->mMultiAgendaView->setDateRange( start, end ); } bool MultiAgendaView::eventDurationHint( QDateTime &startDt, QDateTime &endDt, bool &allDay ) { return d->mMultiAgendaView->eventDurationHint( startDt, endDt, allDay ); } void MultiAgendaView::setIncidenceChanger( Akonadi::IncidenceChanger *changer ) { d->mMultiAgendaView->setIncidenceChanger( changer ); } void MultiAgendaView::updateConfig() { d->mMultiAgendaView->updateConfig(); } void MultiAgendaView::setChanges( EventViews::EventView::Changes changes ) { // Only ConfigChanged and FilterChanged should go from korg->AgendaView // All other values are already detected inside AgendaView. // We could just pass "changes", but korganizer does a very bad job at // determining what changed, for example if you move an incidence // the BaseView::setDateRange(...) is called causing DatesChanged // flag to be on, when no dates changed. EventViews::EventView::Changes c; if ( changes.testFlag( EventViews::EventView::ConfigChanged ) ) { c = EventViews::EventView::ConfigChanged; } if ( changes.testFlag( EventViews::EventView::FilterChanged ) ) { c |= EventViews::EventView::FilterChanged; } d->mMultiAgendaView->setChanges( c | d->mMultiAgendaView->changes() ); } bool MultiAgendaView::hasConfigurationDialog() const { // It has. And it's implemented in korg, not libeventviews. return true; } void MultiAgendaView::showConfigurationDialog( QWidget *parent ) { QPointer dlg( new MultiAgendaViewConfigDialog( d->mMultiAgendaView->calendar()->entityTreeModel(), parent ) ); dlg->setUseCustomColumns( d->mMultiAgendaView->customColumnSetupUsed() ); dlg->setNumberOfColumns( d->mMultiAgendaView->customNumberOfColumns() ); QVector models = d->mMultiAgendaView->collectionSelectionModels(); for ( int i = 0; i < models.size(); ++i ) { dlg->setSelectionModel( i, models[i] ); } QVector customColumnTitles = d->mMultiAgendaView->customColumnTitles(); for ( int i = 0; i < customColumnTitles.size(); ++i ) { dlg->setColumnTitle( i, customColumnTitles[i] ); } if ( dlg->exec() == QDialog::Accepted ) { d->mMultiAgendaView->customCollectionsChanged( dlg ); } delete dlg; } KCheckableProxyModel *MultiAgendaView::takeCustomCollectionSelectionProxyModel() { return d->mMultiAgendaView->takeCustomCollectionSelectionProxyModel(); } void MultiAgendaView::setCustomCollectionSelectionProxyModel( KCheckableProxyModel *model ) { d->mMultiAgendaView->setCustomCollectionSelectionProxyModel( model ); } class MultiAgendaViewConfigDialog::Private { public: MultiAgendaViewConfigDialog *const q; explicit Private( QAbstractItemModel *base, MultiAgendaViewConfigDialog *qq ) : q( qq ), baseModel( base ), currentColumn( 0 ) {} ~Private() { qDeleteAll( newlyCreated ); } void setUpColumns( int n ); AkonadiCollectionView *createView( KCheckableProxyModel *model ); AkonadiCollectionView *view( int index ) const; QVector newlyCreated; QVector selections; QVector titles; Ui::MultiAgendaViewConfigWidget ui; QStandardItemModel listModel; QAbstractItemModel *baseModel; int currentColumn; }; void MultiAgendaView::restoreConfig( const KConfigGroup &configGroup ) { d->mMultiAgendaView->restoreConfig( configGroup ); } void MultiAgendaView::saveConfig( KConfigGroup &configGroup ) { d->mMultiAgendaView->saveConfig( configGroup ); } MultiAgendaViewConfigDialog::MultiAgendaViewConfigDialog( QAbstractItemModel *baseModel, QWidget *parent ) : KDialog( parent ), d( new Private( baseModel, this ) ) { setWindowTitle( i18n( "Configure Side-By-Side View" ) ); QWidget *widget = new QWidget; d->ui.setupUi( widget ); setMainWidget( widget ); d->ui.columnList->setModel( &d->listModel ); connect( d->ui.columnList->selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)), this, SLOT(currentChanged(QModelIndex)) ); connect( d->ui.useCustomRB, SIGNAL(toggled(bool)), this, SLOT(useCustomToggled(bool)) ); connect( d->ui.columnNumberSB, SIGNAL(valueChanged(int)), this, SLOT(numberOfColumnsChanged(int)) ); connect( d->ui.titleLE, SIGNAL(textEdited(QString)), this, SLOT(titleEdited(QString)) ); d->setUpColumns( numberOfColumns() ); useCustomToggled( false ); } void MultiAgendaViewConfigDialog::currentChanged( const QModelIndex &index ) { if ( !index.isValid() ) { return; } const int idx = index.data( Qt::UserRole ).toInt(); d->ui.titleLE->setText( index.data( Qt::DisplayRole ).toString() ); d->ui.selectionStack->setCurrentIndex( idx ); d->currentColumn = idx; } void MultiAgendaViewConfigDialog::useCustomToggled( bool on ) { d->ui.columnList->setEnabled( on ); d->ui.columnNumberLabel->setEnabled( on ); d->ui.columnNumberSB->setEnabled( on ); d->ui.selectedCalendarsLabel->setEnabled( on ); d->ui.selectionStack->setEnabled( on ); d->ui.titleLabel->setEnabled( on ); d->ui.titleLE->setEnabled( on ); // this explicit enabling/disabling of the ETV is necessary, as the stack // widget state is not propagated to the collection views. pprobably because // the Akonadi error overlays enable/disable the ETV explicitly and thus // override the parent-child relationship? for ( int i = 0; i < d->ui.selectionStack->count(); ++i ) { d->view( i )->view()->setEnabled( on ); } } AkonadiCollectionView *MultiAgendaViewConfigDialog::Private::createView( KCheckableProxyModel *model ) { AkonadiCollectionView *cview = new AkonadiCollectionView( 0, false, q ); cview->setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding ); cview->setCollectionSelectionProxyModel( model ); return cview; } void MultiAgendaViewConfigDialog::Private::setUpColumns( int n ) { Q_ASSERT( n > 0 ); const int oldN = selections.size(); if ( oldN == n ) { return; } if ( n < oldN ) { for ( int i = oldN - 1; i >= n; --i ) { QWidget *w = ui.selectionStack->widget( i ); ui.selectionStack->removeWidget( w ); delete w; qDeleteAll( listModel.takeRow( i ) ); KCheckableProxyModel *const m = selections[i]; selections.remove( i ); const int pos = newlyCreated.indexOf( m ); if ( pos != -1 ) { delete m; newlyCreated.remove( pos ); } } } else { selections.resize( n ); for ( int i = oldN; i < n; ++i ) { QStandardItem *item = new QStandardItem; item->setEditable( false ); if ( titles.count() <= i ) { titles.resize( i + 1 ); titles[i] = generateColumnLabel( i ); } item->setText( titles[i] ); item->setData( i, Qt::UserRole ); listModel.appendRow( item ); QSortFilterProxyModel *sortProxy = new QSortFilterProxyModel; sortProxy->setDynamicSortFilter( true ); sortProxy->setSourceModel( baseModel ); KColumnFilterProxyModel *columnFilterProxy = new KColumnFilterProxyModel( sortProxy ); columnFilterProxy->setVisibleColumn( Akonadi::ETMCalendar::CollectionTitle ); columnFilterProxy->setSourceModel( sortProxy ); QItemSelectionModel *qsm = new QItemSelectionModel( columnFilterProxy, columnFilterProxy ); KCheckableProxyModel *selection = new KCheckableProxyModel; selection->setSourceModel( columnFilterProxy ); selection->setSelectionModel( qsm ); AkonadiCollectionView *cview = createView( selection ); const int idx = ui.selectionStack->addWidget( cview ); Q_ASSERT( i == idx ); Q_UNUSED( idx ); selections[i] = selection; newlyCreated.push_back( selection ); } } } bool MultiAgendaViewConfigDialog::useCustomColumns() const { return d->ui.useCustomRB->isChecked(); } void MultiAgendaViewConfigDialog::setUseCustomColumns( bool custom ) { if ( custom ) { d->ui.useCustomRB->setChecked( true ); } else { d->ui.useDefaultRB->setChecked( true ); } } int MultiAgendaViewConfigDialog::numberOfColumns() const { return d->ui.columnNumberSB->value(); } void MultiAgendaViewConfigDialog::setNumberOfColumns( int n ) { d->ui.columnNumberSB->setValue( n ); d->setUpColumns( n ); } KCheckableProxyModel *MultiAgendaViewConfigDialog::takeSelectionModel( int column ) { if ( column < 0 || column >= d->selections.size() ) { return 0; } KCheckableProxyModel *const m = d->selections[column]; d->newlyCreated.erase( std::remove( d->newlyCreated.begin(), d->newlyCreated.end(), m ), d->newlyCreated.end() ); return m; } AkonadiCollectionView *MultiAgendaViewConfigDialog::Private::view( int index ) const { return qobject_cast( ui.selectionStack->widget( index ) ); } void MultiAgendaViewConfigDialog::setSelectionModel( int column, KCheckableProxyModel *model ) { Q_ASSERT( column >= 0 && column < d->selections.size() ); KCheckableProxyModel *const m = d->selections[column]; if ( m == model ) { return; } AkonadiCollectionView *cview = d->view( column ); Q_ASSERT( cview ); cview->setCollectionSelectionProxyModel( model ); if ( d->newlyCreated.contains( m ) ) { d->newlyCreated.erase( std::remove( d->newlyCreated.begin(), d->newlyCreated.end(), m ), d->newlyCreated.end() ); delete m; } d->selections[column] = model; } void MultiAgendaViewConfigDialog::titleEdited( const QString &text ) { d->titles[d->currentColumn] = text; d->listModel.item( d->currentColumn )->setText( text ); } void MultiAgendaViewConfigDialog::numberOfColumnsChanged( int number ) { d->setUpColumns( number ); } QString MultiAgendaViewConfigDialog::columnTitle( int column ) const { Q_ASSERT( column >= 0 ); return column >= d->titles.count() ? QString() : d->titles[column]; } void MultiAgendaViewConfigDialog::setColumnTitle( int column, const QString &title ) { Q_ASSERT( column >= 0 ); d->titles.resize( qMax( d->titles.size(), column + 1 ) ); d->titles[column] = title; if ( QStandardItem *const item = d->listModel.item( column ) ) { item->setText( title ); } //TODO update LE if item is selected } void MultiAgendaViewConfigDialog::accept() { d->newlyCreated.clear(); KDialog::accept(); } MultiAgendaViewConfigDialog::~MultiAgendaViewConfigDialog() { delete d; }